恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
AI智能联系人管理毕设指南:从语义搜索到重复检测的实现
首页
资讯中心
/
AI智能联系人管理毕设指南:从语义搜索到重复检测的实现
AI智能联系人管理毕设指南:从语义搜索到重复检测的实现
发布时间:2026/10/15 0:29:56
简介面向毕业设计场景的PythonAI智能联系人管理项目以Python为主要开发语言借助自然语言处理与机器学习实现联系人高效管理、智能检索及行为预测推荐覆盖添加、编辑、删除、搜索分类与导入导出等功能适合计算机相关专业学生作为课程设计或毕业论文实践参考。整套资源共50个文件压缩包约141.21MB包含7个py源码与10个pyc编译文件、8个ui界面文件、6个html页面及js脚本、3个xml配置、2个xlsx数据表格另有doc使用说明、pdf申请指南、exe可执行程序及jpg/png图片素材从源码阅读到直接运行的完整链路都齐备。开发文档详解系统设计理念、架构与测试案例可执行程序让非开发人员免环境直接体验模板素材用于优化界面观感。已有612人学习下载对于想掌握AI技术如何落地到实际软件的学生而言这份资源既能巩固Python编程能力也能提供完整的工程实践范本。1. 这个题目真正难的不是通讯录而是「AI」两个字怎么交差做过毕业设计的人都有体会导师扫一眼题目「AI智能联系人管理」里真正会被追问的不是联系人表怎么建而是这个「AI」到底体现在哪。只用 LIKE 模糊搜索加一个增删改查撑不起题目里的「智能」但真要接一个大模型接口论文解释不了、演示环境又不稳定。我见过不止一个同学卡在这功能做完了答辩时被一句「你的 AI 算法在哪里」问住。这条路的正解是用轻量但完整的算法把「智能」做成系统里可解释、可演示、可写进论文的模块——语义搜索、重复联系人自动合并、标签自动生成这些技术单拆出来都不难合在一起就是一个能站住脚的毕设核心。这篇文章就按这个思路从选型到代码再到开发文档怎么写、哪些坑必须躲完整拆一遍。2. 技术选型为什么是 Flask SQLite 轻量算法而不是 Django MySQL 大模型2.1 三个能落地的「AI 能力」选型语义搜索、重复合并、自动标签毕设联系人系统的「智能」要想在有限工期里既做得出、又讲得清就得把 AI 落在这类场景依赖少、效果肉眼可见的任务上。第一是语义搜索搜「做芯片的张总」能命中备注里写着「张某某芯片行业深圳」的联系人而不是靠关键词硬匹配第二是重复联系人检测同一人存了两个号码、称呼不一致时系统能识别「李强」和「李强(北京)」是同一个人并提示合并第三是自动标签根据邮箱域名、公司字段、联系频率自动生成「商务」「高校」「活跃」等分组。这三个能力各自只需要一个经典算法——TF-IDF 加余弦相似度、编辑距离加并查集、规则引擎——计算量小本地就能跑而且每一个都能在论文里画出清晰的算法流程图。相比之下接入大模型 API 做智能问答或摘要看似更「AI」但答辩现场网络一断、模型接口一限流整个 Demo 就塌了不划算。2.2 框架选型Flask SQLite 为什么比 Django MySQL 更适合这个题框架选择上常见做法是 Flask 配 SQLite而不是 Django 配 MySQL。原因有三层第一联系人管理的数据量在毕设场景下就是几千条SQLite 单文件存储足够支撑还能省掉 MySQL 的安装配置评审老师把项目拷到别的机器上也能直接跑第二Flask 的代码结构直观ORM 用 SQLAlchemy 独立封装写论文时「表示层—业务层—数据层」三层架构一目了然这是毕业设计评分的加分点第三Django 自带 Admin 后台和 ORM 确实省事但它的「全家桶」结构会让论文里的系统设计章节很难写出自己主导的架构感而且对新手来说Django 的中间件、信号、迁移机制本身就是一个新的学习负担。如果你的选题另外要求了「高并发」或「分布式」那另当别论但就「AI智能联系人管理」这个题目而言Flask SQLite 是把精力留给 AI 模块的最优解。2.3 系统整体模块划分与数据流按照上面的思路整个系统逻辑上拆成四个模块联系人管理增删改查、分组、导入导出、AI 服务语义搜索、重复检测、自动标签、亲密关系提醒、用户界面网页端、数据存储SQLite。调用关系是用户在前端发起请求业务层调用联系人管理模块做常规操作涉及搜索、合并、打标签时再进入 AI 服务模块AI 模块读取数据存储中的联系人数据做计算把结果返回给界面。血缘关系上普通字段的改动只走业务层AI 模块只做「读数据 算结果 回写标记」这样的设计让两边可以并行开发也能在论文里分别论述。下面这张表是依赖版本的一个参考组合实际以你自己环境为准依赖版本建议用途Python3.9 或 3.10运行环境兼容性最稳Flask2.xWeb 框架SQLAlchemy1.4 或 2.xORM 与数据库操作jieba0.42.x中文分词语义搜索的前提scikit-learn1.xTF-IDF 与余弦相似度计算difflib标准库重复名称相似度计算开发环境建议直接用虚拟环境python -m venv venv然后把依赖写进 requirements.txt。这一步很多人跳过结果演示前在教室的机器上装依赖装了四十分钟血泪经验后文避坑章节会单独讲。3. 核心代码落地从数据模型到四个 AI 功能的完整实现3.1 联系人数据模型为什么用关联表存标签而不是一个字段存字符串数据模型是后面所有 AI 功能的地基这里容易犯错的地方是把标签存成「游戏,同学,球友」这样一个逗号分隔字符串。短期内省事但你要做「按标签统计人数」「找出同时属于两个标签的人」这类功能时全部退化成 LIKE 模糊匹配效率低且答辩时会被问住。正确做法是标准的多对多模型联系人表和标签表通过中间关联表相连。用 SQLAlchemy 建模型代码如下from datetime import datetime from sqlalchemy import Column, Integer, String, Text, DateTime, Table, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base Base declarative_base() # 多对多中间表一个联系人可以有多个标签一个标签下可以有多个联系人 contact_tag Table( contact_tag, Base.metadata, Column(contact_id, Integer, ForeignKey(contacts.id), primary_keyTrue), Column(tag_id, Integer, ForeignKey(tags.id), primary_keyTrue), ) class Contact(Base): __tablename__ contacts id Column(Integer, primary_keyTrue, autoincrementTrue) name Column(String(50), nullableFalse, indexTrue) # 姓名必填 phone Column(String(20), indexTrue) # 手机号 email Column(String(120)) # 邮箱 company Column(String(120)) # 公司 address Column(String(200)) # 地址 remark Column(Text) # 备注参与语义搜索 created_at Column(DateTime, defaultdatetime.now) # 创建时间 last_contact_at Column(DateTime, nullableTrue) # 最后联系时间 contact_count Column(Integer, default0) # 联系总次数 favorite Column(Integer, default0) # 是否星标0/1 tags relationship(Tag, secondarycontact_tag, backrefcontacts) class Tag(Base): __tablename__ tags id Column(Integer, primary_keyTrue, autoincrementTrue) name Column(String(30), uniqueTrue, nullableFalse)这段代码有两个关键决策。第一是last_contact_at和contact_count这两个字段它们不为普通展示服务而是为后面的「亲密关系提醒」和「活跃标签」提供数据来源建表时留好免得后期加字段还要改迁移脚本。第二是remark和tags必须进模型因为语义搜索要把它们一起作为检索内容缺一个字段搜索效果都会打折。索引方面name和phone加了indexTrue常规查询走索引数据量到万级也不会卡。3.2 语义搜索用 TF-IDF 加余弦相似度替代 LIKE 模糊查询语义搜索是整个系统里最像「AI」的部分也是答辩时最能讲算法的模块。原理一句话把每个联系人的姓名、公司、备注、标签拼成一段文本用 jieba 分词再用 TfidfVectorizer 把文本转成向量查询时把用户的输入也转成向量最后用余弦相似度排序。这样搜「做芯片的张总」哪怕备注里写的是「张某某在深圳做半导体芯片行业峰会认识的」也能靠「芯片」「半导体」的语义关联命中。核心代码实现如下import jieba import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity def build_corpus(contact_list): 把联系人对象列表转成可检索的文本语料 corpus [] for c in contact_list: # 每个字段用空格隔开避免 jieba 把跨字段的词错误合并 terms [] terms.append(c.name or ) if c.company: terms.append(c.company) if c.remark: terms.append(c.remark) if c.email: terms.append(c.email) for tag in c.tags: terms.append(tag.name) corpus.append( .join(terms)) return corpus def semantic_search(contact_list, query, top_k5, min_score0.05): 语义搜索按相关度返回 [(联系人, 分数)] corpus build_corpus(contact_list) # tokenizerjieba.lcut 让 sklearn 使用 jieba 分词结果 vectorizer TfidfVectorizer(tokenizerjieba.lcut) tfidf_matrix vectorizer.fit_transform(corpus) query_vec vectorizer.transform([ .join(jieba.lcut(query))]) scores cosine_similarity(query_vec, tfidf_matrix).flatten() top_idx np.argsort(scores)[::-1][:top_k] results [] for idx in top_idx: score float(scores[idx]) if score min_score: continue # 低于阈值的结果视为无关 results.append((contact_list[idx], score)) return results逻辑说明build_corpus把每个联系人的多个字段拼成一个文档创建向量时TfidfVectorizer会统计词频并计算逆文档频率高频但无区分度的词如「的」「了」会被自动降权查询时query_vec转成同一个特征空间里的向量cosine_similarity计算两个向量夹角的余弦值越接近 1 越相似。min_score这个阈值要结合实际数据调如果数据里备注文案普遍短0.05 偏低会出现无关结果可以调到 0.1如果备注普遍长0.05 又能保住部分弱相关的合理结果。分词的坑在于 jieba 默认词库对「张总」「王工」这类称呼容易切碎简单办法是在系统启动时加载一次自定义词典把常见称呼和行业词加进去。3.3 重复联系人检测编辑距离加并查集处理「一个人被存了两次」联系人系统里最高频的脏数据问题是同一个人被以不同形式存了两条以上「李强」和「李强(北京)」或者同一手机号挂了两个不同姓名。重复检测要做两件事算出两两之间的相似度再把相似的联系人聚类成一组。选择并查集而不是简单两两标记是因为「A 和 B 相似、B 和 C 相似」时A、B、C 三者应该归为一组这本质是连通分量问题。代码实现如下from itertools import combinations from difflib import SequenceMatcher def name_similarity(a: str, b: str) - float: 基于编辑距离的归一化相似度1 表示完全相同0 表示完全不同 return SequenceMatcher(None, a, b).ratio() def find_duplicate_groups(contacts, name_threshold0.85): 返回并查集 parent 数组parent[i] 相同则属于同一重复组 n len(contacts) parent list(range(n)) def find(x): # 路径压缩保证合并时复杂度接近 O(1) while parent[x] ! x: parent[x] parent[parent[x]] x parent[x] return x def union(x, y): rx, ry find(x), find(y) if rx ! ry: parent[ry] rx # 两两比较满足任一条件即视为同一人 for i, j in combinations(range(n), 2): ci, cj contacts[i], contacts[j] same_phone ci.phone and ci.phone cj.phone same_name name_similarity(ci.name, cj.name) name_threshold if same_phone or same_name: union(i, j) return parent参数要说明白name_threshold0.85是个经验值对中文姓名来说「李强」和「李强(北京)」的相似度约 0.73但在工整数据里「李强」和「李莉」的相似度只有 0.5 左右0.85 能覆盖「带了后缀/前缀」的情况又不至于把同姓不同名的人误合并。same_phone加了ci.phone非空判断避免两个没有手机号的人因为都为空就被判成同一人。实际使用中要保留一份「合并建议列表」让用户确认而不是自动删除数据——毕业设计里「自动合并」直接改数据是高风险操作数据一旦误合并且没提供撤销演示当场翻车。3.4 自动标签与亲密关系提醒不靠 AI 模型的规则实现自动标签的功能定位是「让新录入的联系人自动进入合适的分组」常见做法是规则引擎加少量启发式逻辑。与前面两个模块不同这部分不使用机器学习模型而是基于业务规则的可解释规则系统好处是每条规则都能在论文里列表说明答辩时完全可控。核心实现如下import re from datetime import date, datetime def auto_generate_tags(contact): 根据联系人字段自动推断标签返回标签名列表 tags set() email contact.email or name contact.name or # 规则 1邮箱域名识别身份 m re.search(r([\w.-]), email) if m: domain m.group(1).lower() if domain.endswith(.edu.cn): tags.add(高校) elif domain in (qq.com, 163.com, 126.com): tags.add(个人邮箱) else: tags.add(商务) # 规则 2公司字段存在则视为工作联系人 if contact.company: tags.add(工作) # 规则 3最近 30 天内联系过视为活跃 if contact.last_contact_at: days_since (datetime.now() - contact.last_contact_at).days if days_since 30: tags.add(活跃) # 规则 4联系次数超过 10 次视为高频 if contact.contact_count 10: tags.add(高频) return list(tags)auto_generate_tags在联系人新增或编辑后调用把返回的标签名与现有 Tag 表做 get-or-create 再关联。这里有意不用机器学习因为标签的「标准答案」本身主观规则系统 100% 可控且一眼能看懂。规则阈值是按业务预设的比如「30 天」「10 次」可以在界面上做成可配置项也能写进论文做参数分析。「亲密关系提醒」同理用一个查询找出last_contact_at距今超过 90 天、且历史contact_count 5的联系人在首页提示「你可能很久没联系某人了」这就是可写进文档的 AI 应用场景之一。4. 开发文档怎么写才能让源码和论文互相撑住4.1 毕业设计文档集的标准构成与篇幅参考带「开发文档说明」的题目评审重点不只是代码跑不跑得通还包括文档是否完整、是否与源码一致。一套能过审的文档集通常包含六份开题报告含选题背景与可行性、需求规格说明书、概要设计说明书、详细设计说明书、数据库设计说明、测试报告。正文里写文档要记住一个原则每份文档解决一个特定问题不要在一份文档里既写需求又写实现。篇幅上需求规格 8 到 12 页、概要设计 10 到 15 页、详细设计 15 到 20 页是比较稳妥的区间核心目标是让一个没看过你代码的人仅靠文档就能理解系统在做什么、模块怎么协作、核心算法流程是什么。4.2 需求规格与概要设计先画图、再写字用例图必须先于数据表文档最容易犯的错误是一上来就贴数据库建表语句。正确顺序是先写用例图把「联系人管理」「智能搜索」「重复检测」「标签管理」「提醒」这几个用例画清楚每个用例配上三到五行文字说明再画数据流图标出数据从界面层到业务层再到存储层的流转最后才是 E-R 图和建表语句。写作时常见做法是每张图下配一段 200 字左右的解释把图中元素对应到代码模块比如「用户发起语义搜索请求时请求经 ContactsController 转发至 AIServiceAIService 调用向量化模块计算出相似度排名」。这样的写法在查重和答辩追问时都站得住因为图和代码是逐一对应的。4.3 详细设计文档要跟源码函数逐一对上别写成项目简介详细设计是工作量所在也是很多同学偷工减料的地方。合格的标准是源码里每一个关键函数文档里都要能找到对应说明包括输入参数、输出结果、核心逻辑、异常处理。比如语义搜索模块详细设计里要画一张流程图或写清四步流程分词、向量化、相似度计算、阈值过滤然后贴核心接口定义和关键代码片段。写这部分的态度直接决定评审老师的判断如果文档里出现的函数名在源码里找不到会被认为「文档和程序属于两套东西」这是毕设最伤的评价。5. 调试与避坑联系人系统最容易翻车的 5 个点5.1 中文分词不准语义搜索效果约等于随机排序现象搜索「王老师」返回了一堆无关联系人甚至搜「芯片」出来的结果里没有备注里真正写过芯片的人。原因jieba 默认词典对单字人名和行业简称分词不稳定比如把「张总」切成了「张」和「总」导致查询向量和文档向量匹配不上。解决系统启动时加载自定义词典把常见称呼、行业词、学校名放进去代码用jieba.load_userdict(dict.txt)一行搞定。这个问题属于典型的不踩不知道、一踩全乱套建议在开发初期就准备一份词典边测边补。5.2 SQLite 并发写入导致界面卡死或报错现象同时开两个页面一个在导入联系人一个在编辑标签界面转圈很久甚至报database is locked。原因SQLite 同一时刻只允许一个写事务Flask 开发模式下多个线程并发写就会冲突。解决给 SQLAlchemy 的连接配置connect_args{timeout: 10}增加等待时间同时把写操作集中到一个服务函数里避免琐碎写入频繁发生。如果并发写是应用的常态就说明 SQLite 已经不满足需求但毕设演示场景下加大 timeout 足够。5.3 重复检测阈值调不好要么漏掉重复要么把手足误判成同一人现象阈值设为 0.9 时「李强」和「李强(北京)」没有合并阈值降到 0.6 后「李强」和「李莉」又被标记为可能重复。原因单一阈值无法同时满足所有命名情况。解决把判断逻辑改为双条件兜底——只要手机号完全相同无论姓名相似度高低都直接标记仅当双方都没有手机号时才用姓名相似度阈值。同时阈值不要写死在代码里做成一个可配置项答辩时现场调整对比效果反而加分。5.4 演示环境依赖装不上程序在教室电脑上跑不起来现象换了一台电脑pip install -r requirements.txt报错或者 sklearn 装完后 import 报错找不到动态库。原因没有锁版本或者在有系统 Python 的环境里直接装了依赖破坏了原有环境。解决开发阶段就建虚拟环境并生成 requirements.txt生成时保留顶层依赖的版本号换机器时先建虚拟环境再安装不要图省事全用--user装到系统目录。演示前至少在一台干净机器上完整跑一遍安装流程这一步能救很多人。5.5 开发文档和源码版本不一致答辩被追问时圆不回来现象评阅老师照着详细设计文档里的接口描述提问源码里根本没有这个参数。原因文档是中期写的后来改代码没同步文档最后仓促交稿。解决把「文档更新」写进开发计划每次功能迭代完成时顺手更新对应章节用半小时改文字不要攒到最后一天熬夜同步。特别要注意函数名、数据表字段名这类细节一个不对都可能被当成整体态度问题。6. 交付前的最后一步把 Demo 效果验证做扎实而不是靠运气很多人的毕设演示是「现场随便找一条数据搜一下」这完全是把评分交给随机性。更稳妥的做法是准备一套可复现的演示数据脚本固定随机种子让每次运行的搜索结果、重复检测结果完全一致这样你能提前排练演示文稿里的每一个操作步骤。演示数据里要刻意准备三组效果明显的样例一条备注里写了行业但姓名不含该词的验证语义搜索、两条手机号相同但姓名不完全一致的验证重复合并、一条长期未联系但历史联系频繁的验证提醒功能。验证脚本可以这样写import random from datetime import datetime, timedelta # 固定随机种子保证每次生成的演示数据集一致 random.seed(42) def build_demo_contacts(): demo_data [ {name: 张伟, phone: 13800001111, company: 华芯科技, remark: 负责芯片采购深圳展会认识的, contact_count: 15, last_contact_at: datetime.now() - timedelta(days120)}, {name: 张伟(北京), phone: 13800001111, company: 华芯科技, remark: , contact_count: 3, last_contact_at: datetime.now() - timedelta(days10)}, {name: 李莉, phone: 13900002222, company: , remark: 大学同学目前在高校任教, contact_count: 2, last_contact_at: datetime.now() - timedelta(days5)}, ] return demo_data固定random.seed(42)的意义在于任何涉及随机采样的逻辑比如抽样展示每次跑结果都一样演示时不会出现「刚才能搜出来现在搜不出来」的尴尬。对于语义搜索这类算法我还会额外做一个小验证准备 5 个查询词每个词手工标注期望命中的联系人跑一遍脚本统计 Top-1 命中率把结果写进测试报告。这个习惯帮我提前发现了分词问题而不是在答辩现场被评委的一句「换个词再搜一下」打乱阵脚。当年我因为偷懒没做演示数据脚本演示时正好撞上一个分词失败的关键词当场卡了半分钟才有了后来的这套固定化验证流程。希望帮到你。本文还有配套的精品资源点击获取