
简介基于Python与Django开发的专家信息管理与抽取系统是一份完整的毕业设计源码资源主要面向计算机专业学生、需要完成Web课程设计或毕业设计的开发者。系统围绕专家信息管理场景实现信息录入、查询、修改、删除以及信息抽取功能覆盖Django框架的MTV分层架构、ORM对象关系映射、模板渲染、用户登录认证等核心知识点。压缩包共68个文件包含23个Python源码文件、16个编译缓存文件、14个HTML模板页面、7个JavaScript脚本、3个CSS样式文件以及SQLite数据库、Markdown说明文档和Word文档整体大小仅为180KB结构紧凑便于按目录快速定位代码。目前已有246人学习下载。通过阅读源码可以了解Django项目从模型设计、视图逻辑到前端展示的完整实现流程掌握信息抽取模块的正则表达式与文本处理思路同时参考其中的数据库关联设计和安全防护配置对独立完成同类系统开发或毕业设计答辩都具有直接帮助。1. 专家信息管理系统的真正难点不是增删改查是“抽取”做职称评审、科研项目申报、学会委员遴选这类业务时最烦人的不是维护专家库里的几百条记录而是把一堆半结构化文本变成结构化字段。专家推荐表可能是 Word、PDF、网页表格里面写着“张三男1972年生XX大学教授博士生导师长期从事……方向”你要把姓名、职称、单位、研究方向拆出来塞进数据库。人工复制粘贴一天能做几十条录入人员手指酸数据质量还看心情。这个基于 Python 和 Django 做的《专家信息管理与抽取系统》核心价值就是把“人工读文本、手填字段”这个环节改成“规则抽取 人工复核”。整个系统由两部分组成Django 负责专家信息的管理——增删改查、分类、检索、权限抽取模块负责从非结构化文本里自动识别专家的姓名、单位、职称、研究方向等字段。对新手来说这是一个能完整练到 Django 建模、视图、表单、模板、ORM 查询的实战项目对有经验的开发者来说这套“规则引擎先行、模型后补”的做法是处理垂直领域信息抽取最务实的路线。不建议一上来就上 BERT 或者大模型后面会讲到为什么。2. 技术选型先把地基打牢Django 版本、数据库与抽取策略2.1 为什么选 Django 而不是 Flask管理后台和 ORM 是刚需专家信息管理系统的本质是“内部工具”不是面向 C 端的互联网产品。它要求开发速度快、维护简单、有现成的后台管理界面。Django 自带 Admin 后台模型一注册就能直接增删改查这对内部系统来说省掉了大量前端工作量。Flask 灵活但需要自己拼装 ORM、表单校验、Admin 插件做完一套“够用的”后台时间成本差不多多出三分之一。版本选择上我建议直接用 Django 3.2 LTS 或 4.x 的稳定版本。3.2 是最后一个支持 Python 3.6 的 LTS 版本如果你所在的服务器环境还锁在 Python 3.6就选它如果 Python 版本在 3.8 以上直接上 4.2 或更新的 LTS。不要追最新大版本Django 每次大版本升级都有一些模型字段和 settings 配置的调整内部项目没必要承担这个风险。数据库层面SQLite 在小规模内部系统里完全够用但有一个必须提前知道的限制SQLite 的 LIKE 查询对中文没有大小写问题但对英文字段默认不区分大小写而且模糊查询性能会随着数据量上升明显下降。如果专家库预估会超过五万条或者需要并发写入建议换 PostgreSQL。Django 切换数据库只改 settings 里的 ENGINE 和 NAME代价很低但要在项目一开始就配好等数据灌进去再迁库就麻烦了。2.2 抽取策略确定先规则后模型永远保留人工复核位“抽取系统”这四个字是最容易让人走偏的地方。很多人一听抽取就想到命名实体识别NER、想到 BERT、想到调模型。但专家信息抽取有一个特点字段高度固定格式高度多样。姓名、职称、单位、研究方向这些字段的写法再多也有明确的语言模式。比如职称无非是教授、副教授、研究员、主任医师、正高级工程师、教授级高工这些固定词汇研究方向通常跟着“从事”“研究方向”“长期致力于”这类引导词走。这意味着什么意味着正则表达式和规则模板在这个场景下的准确率就能做到 85% 以上而且可解释、可调试、不需要标注数据。一个内部管理系统的抽取模块最怕的是“模型抽错了但没人知道”。规则抽错了你能看一眼原始文本马上知道哪条规则出了问题模型抽错了你要分析样本、重新训练、再验证一轮下来少说一两天。所以我的建议是第一版抽取模块用正则 词典匹配预留一个抽象接口后续如果需要再接入深度学习模型。这个决策能让项目落地时间压缩一半以上。系统结构上也很清晰正则规则负责“抽得动”词典负责“抽得准”人工复核兜底。2.3 Django 项目结构和 App 拆分管理、抽取、复核三个模块各管各的项目内部我习惯拆三个 Appexperts管专家基础信息和分类extraction管抽取规则、抽取任务和抽取结果review管人工复核流程。新手常犯的错是把所有模型塞进一个 App最后 models.py 一千多行改一个字段牵连一片。experts的模型分两层专家主表存权威数据人工确认过的抽取记录表存每一次抽取的原始文本、命中的规则和抽取结果。这两层数据永远不混在一个表里。设计原则是抽取结果默认是不可信的只有经过人工复核后确认的数据才能写进专家主表。这样即使抽取模块出了问题专家库里已有的数据不会被脏数据覆盖这是整个系统最重要的数据安全设计后面会反复提到。3. 模型设计与抽取框架把“专家信息”和“抽取结果”分开存3.1 专家主表不追求字段齐全追求核心字段可检索专家主表不要一口气设计二十个字段。内部系统的使用场景是录入、查询、导出。业务部门真正高频检索的字段不超过八个——姓名、性别、出生年份、工作单位、职称、学历、研究方向、所在地区。其他像通讯地址、邮箱、电话能填就填但不要作为检索条件。# experts/models.py from django.db import models class Expert(models.Model): 专家主表只存经过确认的权威数据 name models.CharField(姓名, max_length50, db_indexTrue) gender models.CharField(性别, max_length10, choices[(男, 男), (女, 女)], blankTrue) birth_year models.IntegerField(出生年份, nullTrue, blankTrue) organization models.CharField(工作单位, max_length200, db_indexTrue) title models.CharField(职称, max_length50, blankTrue) degree models.CharField(最高学历, max_length20, blankTrue) research_area models.TextField(研究方向, blankTrue) province models.CharField(所在地区, max_length50, blankTrue, db_indexTrue) source_text models.TextField(原始文本, blankTrue, help_text该条记录来自哪段原始材料) created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: ordering [-updated_at] indexes [models.Index(fields[organization, title])] def __str__(self): return f{self.name} - {self.organization}字段上的db_indexTrue是给高频查询字段加的姓名、单位、地区这三个字段会在列表页做搜索和筛选不加索引的话数据量过一万就会出现可感知的卡顿。source_text字段很重要它记录这条专家数据是从哪段材料里来的复核时能直接对照原文避免“数据错了却不知道错在哪”。organization和title联合索引是给“XX大学 教授”这种组合筛选用的。注意gender字段用了choices但数据库层面不会强制校验表单层面 Django 会处理这点够用就行。3.2 抽取记录表每次抽取都留痕规则升级后可以重跑抽取记录表是系统的“黑匣子”。每一次对文本的抽取无论成功还是失败都生成一条记录。这样做至少有三个好处一是能统计抽取覆盖率知道哪些文本别人抽不出二是规则升级后可以批量重跑对比新旧规则的差异三是出了问题能回溯是谁在什么时间用什么规则抽出了错误数据。# extraction/models.py from django.db import models from django.contrib.postgres.fields import JSONField # 若用 SQLite改为 TextField 存 JSON 字符串 class ExtractionRecord(models.Model): 抽取记录原始文本 规则命中信息 抽取结果 source_text models.TextField(原始文本) extracted_data models.JSONField(抽取结果, defaultdict) matched_rules models.JSONField(命中的规则, defaultlist) confidence models.FloatField(置信度, default0.0) status models.CharField( 状态, max_length20, choices[ (pending, 待复核), (confirmed, 已确认), (rejected, 已驳回), ], defaultpending, ) created_at models.DateTimeField(抽取时间, auto_now_addTrue) class Meta: ordering [-created_at]extracted_data存的是抽取出来的结构化字段比如{name: 张三, title: 教授}。matched_rules存命中了哪些规则方便定位是哪条正则抽错了。confidence这个字段看起来简单实际很关键如果你同时命中“教授”和“副教授”两个职称词置信度就要降下来标记为“待复核”而不是随便选一个。状态机设计是pending - confirmed/rejected。人工复核在 Django Admin 里做或者单独做一个复核页面一次显示原文、抽取结果、命中的规则操作员确认或修改后写入专家主表。3.3 注册 Admin 并配置列表页内部系统的后台就是操作台# experts/admin.py from django.contrib import admin from .models import Expert admin.register(Expert) class ExpertAdmin(admin.ModelAdmin): list_display (name, gender, organization, title, province, updated_at) list_filter (province, title) search_fields (name, organization, research_area) list_per_page 50search_fields配置的字段会生成一个搜索框Django 会用icontains做模糊匹配。这里有一个在 SQLite 下容易忽略的坑icontains在 SQLite 里对中文按字符匹配是准确的但对英文是大小写不敏感的——也就是你搜“ABC”能搜出“abc”。业务上通常没问题但如果以后换到 PostgreSQLicontains的语义是大小写敏感还是 insensitive 取决于你用的 collation这个要在换库时重新测试。list_filter按地区和职称筛选业务部门用起来最顺手。list_per_page 50是为了防止一次加载太多行导致页面卡顿别小看这个参数数据过万之后每页默认 100 条会让 Admin 列表页明显变慢。保存配置后运行python manage.py createsuperuser创建管理员账号打开/admin/就能看到后台了。4. 抽取引擎实现正则规则、词典匹配和字段置信度计算4.1 抽取模块的接口设计屏蔽“用什么抽”只暴露“抽没抽到”# extraction/extractor.py import re from abc import ABC, abstractmethod class BaseExtractor(ABC): 抽取器基类所有抽取策略都实现这个接口 abstractmethod def extract(self, text: str) - dict: 输入原始文本返回结构化字段字典 pass class RegexExtractor(BaseExtractor): 基于正则 词典的抽取器 def __init__(self): self.name_patterns [ r姓名[:\s]*([\u4e00-\u9fa5]{2,4}), r([\u4e00-\u9fa5]{2,4})[男女], ] self.title_dict [教授, 副教授, 研究员, 副研究员, 主任医师, 副主任医师, 正高级工程师] self.org_patterns [ r工作单位[:\s]*([\u4e00-\u9fa5()·]{4,50}), r([\u4e00-\u9fa5]{2,30}(?:大学|学院|研究所|研究院|医院|公司)), ] def extract(self, text: str) - dict: result {} for pattern in self.name_patterns: match re.search(pattern, text) if match: result[name] match.group(1) break # 职称词典匹配 上下文约束 for title in self.title_dict: if title in text: result[title] title break # 单位正则 后缀词典 for pattern in self.org_patterns: match re.search(pattern, text) if match: result[organization] match.group(1) break return result这段代码展示了抽取器的三个基本策略。姓名的正则姓名[:\s]*([\u4e00-\u9fa5]{2,4})匹配“姓名张三”这种写法[\u4e00-\u9fa5]是中文匹配范围{2,4}限定了姓名的长度是 2 到 4 个汉字。第二个模式([\u4e00-\u9fa5]{2,4})[男女]匹配“张三男”这种格式把逗号后面的性别作为姓名的边界锚点。职称没有用正则模式直接词典匹配。原因很简单职称是封闭集合常见职称不超过二十个词典匹配的速度和准确率都优于正则。单位匹配用了两层先匹配“工作单位”这种显式引导再用“大学/学院/研究所”等后缀词做隐式匹配。{4,50}限制单位名长度避免贪婪匹配把后面所有文字都吃进去。4.2 置信度计算多个规则命中冲突时宁可低分也不要硬选上面这个抽取器还比较粗实际使用时要把置信度计算加进去。规则抽取最常见的翻车现场是文本里同时出现“XX大学副教授”和“曾在XX大学任教授”词典匹配一口气命中两个职称。此时如果按“找到第一个就停”的策略抽出来的是错的。# extraction/extractor.py 增加置信度逻辑 def _calculate_confidence(self, text: str, result: dict) - float: 根据字段命中情况和冲突情况计算置信度 score 0.0 if result.get(name): score 0.4 if result.get(organization): score 0.3 if result.get(title): # 职称命中多个时降权 title_matches [t for t in self.title_dict if t in text] if len(title_matches) 1: score 0.3 else: score 0.1 result[title_conflict] title_matches # 记录冲突职称列表 return min(score, 1.0)置信度计算的逻辑不复杂每个字段分配权重命中一个加一部分。关键是“职称命中多个”的场景被单独摘出来此时职称字段只加 0.1 分并把冲突列表写进结果里。总分低于 0.7 的记录自动标记为“待复核”这样操作员可以优先处理低置信度记录高置信度的批量确认。实际项目中置信度阈值不是一个固定值要根据业务容忍度调。如果抽出 100 条里允许错 1 条阈值可以调高到 0.8如果只是粗筛0.5 就够了。我一般把阈值放在 Django settings 里而不是硬编码在代码中方便随时调整。4.3 对整批文本批量抽取用 Django management command 跑批单条文本抽取没问题后就该考虑批量导入的场景。业务部门手里往往是一批 Word 转出来的文本文件几十上百篇。写一个 Django management command让用户从后台管理页面上传一个 zip 包解压后逐篇抽取。# extraction/management/commands/batch_extract.py import zipfile from pathlib import Path from django.core.management.base import BaseCommand from extraction.models import ExtractionRecord from extraction.extractor import RegexExtractor class Command(BaseCommand): help 批量抽取 zip 包内的文本文件 def add_arguments(self, parser): parser.add_argument(zip_path, typestr, help包含文本文件的 zip 包路径) def handle(self, *args, **options): extractor RegexExtractor() zip_path options[zip_path] with zipfile.ZipFile(zip_path) as zf: for member in zf.namelist(): if not member.endswith(.txt): continue raw_text zf.read(member).decode(utf-8, errorsignore) extracted extractor.extract(raw_text) confidence extractor._calculate_confidence(raw_text, extracted) ExtractionRecord.objects.create( source_textraw_text, extracted_dataextracted, confidenceconfidence, ) self.stdout.write(f已处理: {member})errorsignore是处理中文文本时的关键参数。Word 转出的 txt 文件经常带 BOM 头或者混入非法编码这段decode(utf-8, errorsignore)会跳过无法解析的字节而不是抛异常保证一个文件出错不会中断整批任务。跑批命令是python manage.py batch_extract ./uploads/experts.zip。处理完成后打开 Django Admin 的 ExtractionRecord 列表按confidence升序排列优先复核低分记录。这里要强调批量抽取的产出是待复核记录不是专家主表数据。很多新手做完抽取直接写入专家库这是整个项目里最危险的坏习惯。5. 避坑指南专家信息抽取系统最常见的五个事故现场5.1 SQLite 下模糊查询和并发写入的隐性限制我在测试环境用 SQLite 跑通一切后部署到服务器上发现两个问题一是搜索“王”时返回的列表里混入了“王五”和“王小二”这在业务上没问题但搜索“主任”时会漏掉“副主任医师”因为icontains匹配的是“包含”而不是“以…开头”二是多个用户同时在后台确认专家记录时偶尔会报 “database is locked” 错误。SQLite 在写并发场景下锁粒度太大内部系统虽然并发不高但一旦多个人同时操作就会出现。解决方法是上线前把数据库切到 PostgreSQLDjango 的配置迁移代价很小# settings.py DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: expert_db, USER: expert_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 5432, } }换成 PostgreSQL 后“database is locked” 不会再出现icontains的模糊查询也走 PostgreSQL 的索引机制性能会比 SQLite 好一个档次。数据库层面的切换动作很小但必须在灌数据之前完成迁移工具在项目启动阶段用最省力。5.2 Django ORM 误用 save(update_fields) 导致并发覆盖设计模型时我提到抽取结果默认不可信需要人工确认后写入专家主表。这个“写入”动作用到了 Django ORM 的save(update_fields[...])来做到只更新指定字段。我用一个实际踩过的坑说明它为什么必须配合代码规范使用。某次我把复核页面里的保存逻辑写成expert.save()也就是不带参数结果一段本来只修改了organization的操作把另一名同事基于同一条专家记录更新的research_area字段覆盖成了旧值。原因在于expert.save()会把所有字段都写回数据库而复核页面加载的专家对象是几分钟前查出来的字段值停留在旧状态。# review/services.py def confirm_extraction(record_id: int, user_edited_fields: dict) - None: 确认一条抽取记录并写入专家主表。 只更新复核人显式确认/修改过的字段避免并发覆盖。 record ExtractionRecord.objects.get(idrecord_id) expert, created Expert.objects.get_or_create(namerecord.extracted_data[name]) sensible_fields {organization, title, degree, research_area} update_fields [] for field, value in user_edited_fields.items(): if field in sensible_fields: setattr(expert, field, value) update_fields.append(field) if created: expert.save() # 新记录全字段写入 else: expert.save(update_fieldsupdate_fields) # 只更新确认过的字段同样是在save方法上做文章区别是运行时行为完全不同。带update_fields的保存会让 SQL 变为UPDATE ... SET organization%s WHERE id%s不带则会更新所有字段。实际业务里把“复核确认”和“修改某个字段”视为两个操作各自写好独立函数不要混在一个入口里。5.3 正则表达式贪婪与回溯导致的长文本性能灾难抽取器的正则里最容易忽略的是贪婪匹配和回溯陷阱。我最初写单位匹配时用的是(.*?)非贪婪模式看起来没问题但遇到文本里有两个“工作单位”时非贪婪模式会匹配到第一个就停改为贪婪模式(.*)后又出现匹配结果把整段正文都吞进organization字段的情况。结症在{4,50}这个长度限制上。正则引擎先尝试尽量多地匹配然后回溯到最长的合法结果。在长文本里这个回溯过程可能非常耗时严重时一段 5000 字的文本能卡住好几秒。添加长度上限是必要的同时要配合re.DOTALL的取反思路——单位名不会包含换行符所以用[^\n]{4,50}代替[\u4e00-\u9fa5()·]{4,50}会更安全org_patterns [ r工作单位[:\s]*([^\n]{4,50}), r([\u4e00-\u9fa5]{2,30}(?:大学|学院|研究所|研究院|医院|公司)), ][^\n]匹配除换行符外的任意字符既解决了跨行误匹配也把单行的匹配范围限制在了一行文本内性能大幅提升。这类问题在调试时不容易发现因为短文本下表现正常一换真实的长篇专家介绍文本就开始卡。上线前建议做一次性能测试把代表性文本逐条跑一遍。5.4 编码问题Word 转换出的文本常带 BOM 头和全角符号批量抽取时最隐蔽的坑是文件编码不一致。有的 txt 文件是 UTF-8 带 BOM有的是 GBK还有的是 UTF-16。直接按utf-8解码GBK 文件就报UnicodeDecodeError。解决方法是不要盲目按utf-8解码而是做编码探测# batch_extract 中替换直接 decode 的逻辑 def decode_text(raw_bytes: bytes) - str: 尝试多种编码保证批量任务不中断 for encoding in [utf-8-sig, utf-8, gbk, utf-16]: try: return raw_bytes.decode(encoding) except UnicodeDecodeError: continue return raw_bytes.decode(utf-8, errorsignore)注意utf-8-sig放在第一位它会自动处理 BOM 头。全角符号是另一个问题比如“姓名张三”里的冒号是中文全角让正则匹配失败。有两种处理方式在正则里写成[:\s]*同时匹配全角半角或者在抽取前先做一次文本清洗把全角标点统一转为半角。我通常两个都做清洗负责数据预处理正则保持兼容两种写法双保险。5.5 手动在 Admin 里改数据后忘了留痕“后悔药”给不了Admin 后台虽然方便但它默认不记录修改历史。业务人员复核时手动在 Admin 里改错一个字段事后想追溯是谁在什么时候改的完全无从下手。这个问题在内部系统里很常见你用着顺手但操作审计一片空白。Django 生态里django-simple-history可以给模型加历史记录表每次保存自动记录快照。如果不想引入依赖也可以在 Expert 模型里加一个historyJSONField在每次save()时把旧值和新值附加进去# experts/models.py 扩展 history models.JSONField(修改历史, defaultlist) def save(self, *args, **kwargs): if self.pk: old Expert.objects.filter(pkself.pk).first() if old: changes {} for field in self._meta.fields: if getattr(old, field.name) ! getattr(self, field.name): changes[field.name] {old: getattr(old, field.name), new: getattr(self, field.name)} if changes: self.history.append({at: datetime.now().isoformat(), changes: changes}) super().save(*args, **kwargs)用_meta.fields遍历所有字段做新旧对比把变化的字段记录下来。这种方式会轻微增加每次保存的开销但内部系统的写入频率不高完全可接受。这是个典型的“后悔药”功能平时不起眼出事后才知道钱花在哪。6. 从规则引擎到模型推理抽取系统的进阶路线与验证方法规则抽取做到 85% 准确率后剩下的 15% 是硬骨头——那些文本表述不规范的比如“目前担任 XX 大学前沿交叉研究院执行院长二级教授博导”。这里的“二级教授”不在词典里规则抽不出来。这时候再来考虑接模型。在动手之前先要回答一个问题剩下的 15% 值得投入多少成本如果业务上能接受“人工复核兜底”规则引擎就够用了如果需求是自动入库、减少人工量那下一步是引入号称“专家信息抽取”的专用模型。这里有个实用的折中路线规则引擎 词典增量维护。团队里最了解业务的人每月把新遇到的职称词、单位后缀词补充进词典抽出率会慢慢逼近 92%而成本只是几分钟的词典维护。真到了要上模型那一步建议顺序是先用现成的中文分词工具如 jieba做候选词识别再用一个二分类器判断候选词是否属于职称/单位最后才是微调一个轻量级 NER 模型。评估指标就一个F1 值——精确率和召回率的调和平均。不要只看准确率因为“抽不出来”和“抽出错的”都会让业务人员抓狂。我给自己定过一个规矩任何抽取策略上线时必须同步做一份验证集——从真实业务文本里抽 200 条人工标注标准答案每次改完规则后批量跑一遍。这 200 条验证集是抽样系统的“体检报告”。我见过太多团队改一条正则修好了一个字段的匹配却把另一个字段的准确率打掉五个点全因为在验证集上跑才知道。微软的培根说过“阅读使人充实会谈使人敏捷写作使人精确”放在系统建设里验证集就是那个让你精确的东西。做这个项目最大的教训就是不要在规则引擎上追求 100% 的完美完美是不存在的把 85% 交给规则、14% 交给人工复核、1% 留给以后更好的模型这个比例才是内部系统最舒服的姿态。希望这套从模型设计到批量抽取再到复核闭环的思路能帮你在做类似信息抽取系统时少走一段冤枉路。本文还有配套的精品资源点击获取