☰
基于Django的专家信息管理与抽取系统全解析
2026/10/30 16:41:44 网站建设 项目流程

简介:基于Python与Django框架的专家信息管理与抽取系统毕业设计源码,适用于计算机专业毕业生、Web应用开发学习者以及需要快速搭建管理类项目的开发者。系统实现专家信息录入、查询、修改、删除,并结合信息抽取技术从非结构化文本中识别关键内容,涉及Django MTV架构、ORM映射、模板渲染、用户认证与安全防护等核心点,可帮助读者理解真实项目的分层设计与开发流程。

压缩包内共68个文件,包含23个Python源码、16个编译后的pyc文件、14个HTML页面模板、7个JavaScript脚本与3个CSS样式表,同时附带SQLite数据库及说明文档(md、docx、txt),整体约180KB。项目目录按模块划分,配置、路由、视图、模型及静态资源结构清晰,便于快速定位和二次修改。

目前已有246人学习使用,适合作为毕业设计参考或Web开发实训。通过源码可掌握数据库设计、后台逻辑与前端渲染的集成方式,并能直接运行系统测试信息抽取流程,是一份完整性较高的Django实战案例。

1. 专家信息管理与抽取系统:毕业设计里那个“能跑起来”的 Django 项目

如果手头有一个基于 python 和 Django 的《专家信息管理与抽取系统》源码包,第一件事不是急着跑起来,而是先看清楚它到底替你做了什么。这个系统的核心场景很直接:把散落在网页、Word 简历、PDF 通知里的专家信息,经过抽取、清洗、去重后落进数据库,再通过管理后台完成维护和检索。适合三类人:拿它做毕业设计的在校生、需要快速搭一套评审专家库的内部项目、以及刚学 Django 想找完整项目练手的开发者。下面按我实际拆过的流程,把这套东西从结构到运行讲清楚,该给参数的给参数,该排错的排错。

2. 先看懂项目骨架:Django 目录结构与专家模型的数据库设计

拿到一个 Django 源码包,我习惯先看目录,再读 models.py,最后才碰视图和模板。因为模型决定了系统能存什么,目录结构决定了你改起来顺不顺手。这个系统的代码量不大,但该有的 Django 工程元素都在:一个项目配置目录、一个业务 app、模板目录和静态文件目录。

2.1 项目布局与 settings.py 里三个不能动的配置

典型的项目结构长这样:

expert_system/ ├── manage.py ├── config/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── experts/ │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── admin.py │ ├── extract/ │ │ ├── regex_rules.py │ │ └── document_parser.py │ └── migrations/ ├── templates/ │ ├── base.html │ └── experts/ └── static/

config 是项目配置目录,experts 是业务 app,extract 子包专门放抽取逻辑。这样拆分的好处是:抽取规则变了只改 extract,页面变了只改 templates,互不干扰。

settings.py 里三个配置我每次都会先确认,特别是从别人手里接项目时:

INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "experts", # 业务 app 必须注册 ] DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } } LANGUAGE_CODE = "zh-hans" TIME_ZONE = "Asia/Shanghai" USE_TZ = True

注意三个点:第一,experts 没写进 INSTALLED_APPS,迁移命令会直接忽略这个 app,表根本建不出来;第二,开发阶段默认 SQLite 没问题,但如果你要接 MySQL,ENGINE 要换成 django.db.backends.mysql,还得配 NAME、USER、PASSWORD、HOST、PORT;第三,LANGUAGE_CODE 和 TIME_ZONE 不改成中文的话,admin 后台显示英文菜单,且时间差 8 小时,看着很别扭。USE_TZ 保持 True,存到库里的是 UTC 时间,展示时 Django 会自动转本地时间,这个行为不要乱改,改了反而会出时区错乱。

2.2 专家信息表设计:字段、约束与多对多标签

这个系统的核心模型是专家信息表。字段设计我拆过很多版本,好的设计一定包含“基本信息 + 联系信息 + 履历文本 + 状态标记 + 时间戳”。参考实现如下:

from django.db import models class ResearchField(models.Model): name = models.CharField(max_length=50, unique=True, verbose_name="研究领域") class Meta: verbose_name = "研究领域" verbose_name_plural = "研究领域" def __str__(self): return self.name class Expert(models.Model): STATUS_CHOICES = ( ("pending", "待复核"), ("active", "已入库"), ("invalid", "已失效"), ) name = models.CharField(max_length=50, db_index=True, verbose_name="姓名") title = models.CharField(max_length=50, blank=True, null=True, verbose_name="职称") org = models.CharField(max_length=200, verbose_name="所在单位") phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="联系电话") email = models.EmailField(blank=True, null=True, verbose_name="邮箱") profile = models.TextField(blank=True, null=True, verbose_name="履历") fields = models.ManyToManyField(ResearchField, blank=True, verbose_name="研究领域") source = models.CharField(max_length=255, blank=True, null=True, verbose_name="信息来源") status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="pending") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") class Meta: verbose_name = "专家信息" verbose_name_plural = "专家信息" ordering = ["-updated_at"] def __str__(self): return self.name

几个关键设计点。phone 用 CharField 而不是 IntegerField,因为手机号前面可能有 86 或 +86 前缀,整数类型存不下且会丢前导零;name 加了 db_index,因为检索专家时最常用的条件就是姓名,索引能明显加快查询;status 用 choices 做枚举,比随意字符串更规范,后面做抽取结果人工复核时直接按状态过滤;fields 是多对多关联 ResearchField,一个专家可以属于多个领域,一个领域也能挂多个专家,这是比用逗号拼接字符串更合理的设计,后面做“按领域筛选专家”只要一条 ORM 查询。profile 用 TextField 存完整履历,别用 CharField,因为履历可能上千字。

2.3 数据表迁移与 admin 注册:先让后台能把人存进去

模型建好之后,走一遍 Django 的标准迁移流程:

python manage.py makemigrations experts python manage.py migrate

makemigrations 会读取 models.py 的变化并生成迁移文件;migrate 把迁移文件真正执行到数据库。第一次跑如果报 “No changes detected”,多半是 INSTALLED_APPS 没注册 app,回 2.1 检查。migrate 成功后再注册 admin,后台才能直接维护数据:

from django.contrib import admin from .models import Expert @admin.register(Expert) class ExpertAdmin(admin.ModelAdmin): list_display = ("name", "title", "org", "phone", "status", "updated_at") list_filter = ("status", "fields") search_fields = ("name", "org", "profile") list_per_page = 20 autocomplete_fields = ["fields"]

list_display 控制列表页显示的列,phone 和 status 放在列表页能让你一眼看出哪些记录缺手机号、哪些还在待复核;list_filter 按状态和研究领域做侧边栏过滤,数据量大了以后这是最高效的筛选方式;search_fields 里填 name、org、profile,admin 顶部的搜索框会同时在姓名、单位和履历里模糊匹配。之所以给 ResearchField 也在 admin 里注册并配套 search_fields,是因为 autocomplete_fields 依赖目标模型的 search_fields,不配上会报错。到了这一步,系统已经具备“人工录入 + 后台检索”的基础能力,接下来要解决的是“怎么让文本里的专家信息自动进库”。

3. 把文本变成结构化专家档案:基于正则与文档解析的信息抽取实现

信息抽取是这个系统的重头戏,也是名字里“抽取”二字的来源。先说清楚边界:抽取只针对你有权使用的数据源,比如自己整理的人员名录、已经公开的评审专家公示、内部维护的简历库。不要拿它去批量采集陌生平台上的个人信息,这个方向不符合使用前提。

3.1 抽取链路:从原始文本到数据库记录要经过哪四步

整条抽取流程我一般拆成四步:文本获取、结构化提取、字段清洗、入库去重。

文本获取解决的问题是“内容从哪来”。网页正文、PDF 附件、Word 简历都可能成为输入源。结构化提取用正则或者文档解析把姓名、电话、邮箱、职称从自由文本里挑出来。字段清洗处理脏数据,比如电话里的横杠和空格、履历里的换行符、姓名前后的空白。入库去重则解决“同一个人被抽了两遍”的问题,通常用姓名+单位联合判断。

这四步对应到 Django 代码里,可以封装成一个独立服务层。views 里只负责接收请求,抽取逻辑全部放 experts/extract/ 下。这样做的好处是你可以在 Django shell 或管理命令里单独测试抽取服务,不用每次走 HTTP 请求。

3.2 正则规则实战:姓名、手机号、职称与邮箱的匹配写法

正则是最直接的抽取手段。这个系统里最常用的几条规则我整理如下:

import re def extract_phone(text): # 匹配 1 开头、第二位 3-9、共 11 位的手机号 pattern = re.compile(r"(?<!\d)1[3-9]\d{9}(?!\d)") matches = pattern.findall(text or "") return list(set(matches)) def extract_email(text): # 常规邮箱格式,避免匹配到带后缀的乱码串 pattern = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}") matches = pattern.findall(text or "") return list(set(matches)) def extract_title(text): # 按优先级取职称,顺序很重要 keywords = ["教授", "副教授", "研究员", "副研究员", "高级工程师", "主任医师"] for kw in keywords: if kw in (text or ""): return kw return None

几个细节值得解释。手机号正则两端加了 (?<!\d) 和 (?!\d),也就是负向后顾和负向前瞻,目的是防止把“123456789012”这种 12 位数字串的中间 11 位误判成手机号;findall 返回列表,set 去重避免同一份文本里重复提及;邮箱正则是常用写法,% 和 + 都用转义包裹,避免把它当成正则量词。职称抽取我用“按优先级遍历”而不是一次性 findall,因为一份履历里可能同时出现“副教授”和“研究员”,你总得定一个优先取谁的值。上面代码里教授排在第一位,是因为它资历最高,实际项目里你可以把顺序改成业务上最看重的职称。

需要注意,正则只能解决格式固定的字段。姓名抽取用正则不可靠,中文姓名没有固定规律,我一般不用正则抽姓名,而是结合上下文规则:比如“姓名:张三”“张三,男,博士”这类把姓名字段放在行首的格式。更通用的做法是让用户上传结构化名单,或者从 Word/PDF 里按标题栏位提取。

3.3 处理 PDF 和 Word 简历:pdfplumber 与 python-docx 的配合

专家信息的常见载体是 PDF 通知和 Word 简历表。PDF 提取我用 pdfplumber,Word 用 python-docx。

import pdfplumber def extract_pdf_text(path): text_parts = [] with pdfplumber.open(path) as pdf: for page in pdf.pages: page_text = page.extract_text() if page_text: text_parts.append(page_text) return "\n".join(text_parts)

pdfplumber 按页读取,每一页的 extract_text() 返回该页的纯文本;如果页面上是扫描图片而不是文字层,需要配合 OCR,pdfplumber 不处理这种场景。遇到空白页返回 None,所以要做 if page_text 判断。

from docx import Document def extract_docx_text(path): doc = Document(path) lines = [p.text for p in doc.paragraphs] for table in doc.tables: for row in table.rows: for cell in row.cells: lines.append(cell.text) return "\n".join(lines)

python-docx 的 doc.paragraphs 只能取到段落文本,简历里的个人信息经常被放在表格里,所以必须额外遍历 doc.tables,把每个单元格的文本也追加进内容列表。我见过不少翻车案例,就是只读了段落,结果姓名和电话全丢,最后抽取结果全是空值。另外跑循环时要留意:遍历 doc.tables 的 cell 时,因为 merge 单元格会被重复计入,建议拿 cell.text 之后去重一次,否则同一个人的履历会出现重复拼接。

3.4 入库前的清洗:去重、字段截断与空值兜底

抽取出来的字段直接入库会出问题。电话可能是“138-1234-5678”,履历里有连续多个换行,邮箱可能带分号。所以入库前必须过一遍清洗逻辑:

import re def normalize_phone(raw): digits = re.sub(r"\D", "", raw or "") # 只保留末尾 11 位,兼容 86 前缀 return digits[-11:] if len(digits) >= 11 else "" def clean_profile(text): text = re.sub(r"\s+", " ", text or "").strip() return text[:500] def save_expert(name, org, phone, title, profile, source): norm_phone = normalize_phone(phone) clean_text = clean_profile(profile) obj, created = Expert.objects.get_or_create( name=name.strip(), org=org.strip(), defaults={ "phone": norm_phone, "title": title, "profile": clean_text, "source": source, "status": "pending", }, ) return obj, created

normalize_phone 先把所有非数字字符删掉,再取末尾 11 位,这样“+86 138-1234-5678”会被统一成 13812345678;但注意电话号码是座机时这个规则不成立,座机应该保留区号和分机,建议对 phone 字段先判断长度再决定走哪个分支。get_or_create 的查询条件是 name + org,只要这两个字段匹配,就认为是同一个人,新抽到的信息不会重复建记录;defaults 里放的字段只在创建时写入,已有记录不会覆盖旧数据。这样处理的好处是,履历更新了但姓名没变时,旧记录保持原样,新信息进待复核队列,避免想当然覆盖掉人工作过修正的数据。

这里也暴露了系统的一个关键原则:抽取结果只是辅助录入,不应该全自动覆盖。status 默认 pending,就等服务端人工复核,这个机制后面还会讲到。

4. 从零复现运行:环境配置、数据库迁移与全流程验证

源码包拿到手,最关心的问题永远是“能不能跑起来”。我按干净环境的标准走一遍完整流程。系统基于 Python 3.8 和 Django 3.2 LTS,这两个版本组合最稳,网上能找到的资料也最多,别上来就装 Django 5,很多老项目代码在 5.x 下会报错。

4.1 创建虚拟环境与安装依赖:版本锁定比“最新版”更省事

首先确认 Python 版本,再建虚拟环境装依赖:

python --version # 推荐 3.8 - 3.11,毕设项目通常基于 3.8 python -m venv venv source venv/bin/activate pip install -r requirements.txt

requirements.txt 里通常包含 Django、pdfplumber、python-docx、pymysql 这几个。如果源码包没带 requirements.txt,我一般手动固定版本安装:

pip install Django==3.2.25 pip install pdfplumber==0.10.4 pip install python-docx==0.8.11 pip install PyMySQL==1.0.2

为什么强调版本锁定?因为 Django 3.2 和 Django 4.2 在数据库连接、URL 路由写法上有兼容差异,pdfplumber 0.10 和 0.11 的 API 也略有变化。装最新版大概率能跑,但报错时你会陷入“代码没问题、环境有问题”的排查泥潭。与其追新,不如看源码包里有没有 requirements.txt,没有就手动锁这一套,这是最省事的路径。安装完成后用 pip list 看一眼已安装的包,确认 Django 版本确实是 3.2.x。

4.2 初始化数据库:migrate、createsuperuser 与初始数据

依赖装好后,依次执行迁移和创建管理员:

python manage.py makemigrations experts python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000

makemigrations 针对 experts app 生成迁移文件,migrate 把所有未执行的迁移应用到数据库。createsuperuser 会交互式地让你输入用户名、邮箱和密码,这个账号用来登录 admin。runserver 加 0.0.0.0:8000 是为了让局域网内其他机器也能访问,方便你在另一台电脑上测试页面;如果只是本机调试,直接 runserver 就行。

启动后浏览器打开 http://127.0.0.1:8000/admin/,用刚才创建的账号登录。看不到中文界面就检查 2.1 里的 LANGUAGE_CODE,数据库连接报错就检查 DATABASES 配置,admin 样式丢失的问题留到第 5 章排查。这一步走通了,说明项目基础环境没问题,接下来验证抽取功能。

4.3 启动服务并跑通三条验证路径:后台录入、抽取入库、前台检索

我验证一个 Django 项目不会只点开页面看看,至少跑三条路径:后台直接录入、命令行触发抽取、前台检索结果。

后台录入路径:登录 admin,新增一个专家,填姓名、单位、电话、履历,保存,刷新列表能看到状态为待复核的记录。这条路径验证模型和 admin 配置正确。

抽取路径:先在项目根目录下建一个 data/ 文件夹,放一份简历 PDF,然后用 Django 管理命令触发抽取。管理命令是验证抽取逻辑最干净的方式,代码如下:

# experts/management/commands/import_experts.py from django.core.management.base import BaseCommand from experts.extract.document_parser import extract_pdf_text from experts.extract.regex_rules import extract_phone, extract_email, extract_title from experts.models import Expert class Command(BaseCommand): help = "从指定 PDF 文件抽取专家信息并入库" def add_arguments(self, parser): parser.add_argument("file_path", type=str) def handle(self, *args, **options): file_path = options["file_path"] text = extract_pdf_text(file_path) phone_list = extract_phone(text) email_list = extract_email(text) title = extract_title(text) # 简易姓名提取:取第一个非空文本行的前两个字段 first_line = text.split("\n")[0] if text else "" name = first_line.split()[0] if first_line else "未知" org = "待补充" obj, created = Expert.objects.get_or_create( name=name, org=org, defaults={ "phone": phone_list[0] if phone_list else "", "email": email_list[0] if email_list else "", "title": title, "status": "pending", }, ) self.stdout.write(f"created={created} expert={obj.name}")

调用方式:

python manage.py import_experts data/example.pdf

命令结构要注意:management/commands 目录下必须有init.py;add_arguments 里定义的参数名,handle 里通过 options["file_path"] 读取,参数名不要写错,否则命令行会报 unrecognized arguments。这里的姓名提取是简版逻辑,真实项目里姓名通常在简历第一行,但也会有反例,所以抽取完成后必须人工复核,这也是状态默认 pending 的原因。

前台检索路径:如果系统里有专家列表页,打开首页搜索框输入“教授”,看能否筛出对应记录。验证的是视图、URL 路由和模板渲染是否正常。

三条路径全通,说明系统在功能层面没问题。如果某一条挂了,优先看浏览器控制台、Django 服务端日志和数据库记录,绝大多数问题都能在五分钟内定位。

5. 避坑记录:Django 版本、数据库驱动与中文编码的五个翻车现场

运行和二次开发过程中踩过的坑,我按“现象 → 原因 → 解决”整理出来。这些坑不是偶发,而是 Django 项目里出现频率极高的五类问题,提前知道能省一天排查时间。

5.1 Python 3.8 与 Django 版本不匹配,迁移命令直接报错

现象:python manage.py migrate 时报错 django.core.exceptions.ImproperlyConfigured,提示 Django 版本需要升级,或直接抛 AttributeError,内容指向 pathlib 或 asgiref。

原因:Django 每个大版本对 Python 版本有明确支持矩阵。Django 4.1 以上要求 Python 3.10,Django 5.0 要求 Python 3.10+;如果系统环境是 Python 3.8,却装了 Django 5,迁移命令必然失败。

解决:先确认 Python 版本,然后锁定 Django 3.2 LTS 或者 4.2 LTS。最稳的做法是直接用命令固定版本:

pip install Django==3.2.25 python -c "import django; print(django.get_version())"

打印出的版本是 3.2.x 再继续跑迁移。检查任何 Django 源码包时,第一件事应该是读 requirements.txt,里面的版本号就是作者验证过的组合,不要自作主张升级。

5.2 pymysql 装好了,数据库还是连不上

现象:settings.py 里配置了 MySQL,pymysql 也 pip install 了,但 migrate 报错 AttributeError: module 'pymysql' has no attribute 'install_as_MySQLdb',或者报 ModuleNotFoundError: No module named 'MySQLdb'。

原因:Django 原生使用 MySQLdb 作为 MySQL 驱动,而 MySQLdb 在 Python 3.x 下兼容性很差,所以大家改用 PyMySQL 模拟 MySQLdb。但 PyMySQL 的 install_as_MySQLdb() 必须在项目初始化阶段执行,否则 Django 找不到 MySQLdb。

解决:在项目配置目录的init.py 里加两行:

import pymysql pymysql.install_as_MySQLdb()

然后确认 settings.py 里的 DATABASES 的 ENGINE 是 django.db.backends.mysql,并且 NAME、USER、PASSWORD、HOST、PORT 都填对了。如果跑了初始化还是报错,检查 MySQL 版本和用户权限,一个常见问题是 root 账号不允许从 127.0.0.1 连接,需要单独授权。

5.3 表单提交被 CSRF 校验拦截,抽好的数据进不了库

现象:在前台页面提交专家信息表单,返回 403 Forbidden,页面标题显示 CSRF verification failed。

原因:Django 默认开启 CSRF 中间件,页面里的 form 表单没有携带 {% csrf_token %},服务端拒绝本次请求。毕设项目里很多人只写了 form 和按钮,忘了加 csrf_token。

解决:在模板的 form 标签内,紧跟在

后面加:
<form method="post"> {% csrf_token %} <!-- 其余字段 --> </form>

还有一种情况:用 Django REST Framework 做 AJAX 提交时,需要在请求头带 X-CSRFToken,这个不在本系统范围内,但如果你改造成了前后端分离,必须处理。调试时如果只想临时跳过验证,可以注释掉中间件 config/settings.py 里的 django.middleware.csrf.CsrfViewMiddleware,但正式环境千万别这么干,这是给攻击者开大门。

5.4 中文姓名和履历乱码,根源是 utf8mb4 没开

现象:admin 后台录入中文正常,但通过 MySQL 导入的数据进入页面全是问号(?),或者命令行操作时中文显示乱码。

原因:MySQL 数据库或数据表字符集用了默认的 latin1,或者建表时没指定 utf8mb4。utf8mb4 是 MySQL 下覆盖表情符和四字节字符的完整 UTF-8 实现,而 utf8mb3 存不了部分生僻字和 Emoji。

解决:建库时显式指定字符集,并在 settings.py 里把连接字符集也指定:

CREATE DATABASE expert_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "expert_db", "USER": "root", "PASSWORD": "your_password", "HOST": "127.0.0.1", "PORT": "3306", "OPTIONS": { "charset": "utf8mb4", }, } }

如果数据已经进库了,还要对存量表做转码。用 ALTER TABLE 逐表修改字符集比较繁琐,我一般直接在迁移之前就检查一遍连接参数,避免数据脏掉之后再来回迁。

5.5 admin 后台样式丢失,静态文件收集没做

现象:登录 admin 后页面纯文本,CSS 完全没加载,浏览器控制台报一堆 .css 和 .js 404。

原因:Django 开发服务器在 DEBUG=True 时自动托管静态文件,但 admin 的静态文件是从 django.contrib.admin 包里的 static 目录映射出来的;当你关掉 DEBUG 或者放在 Nginx 后面时,必须手动执行 collectstatic,把散在各 app 的静态文件复制到 STATIC_ROOT。

解决:在 settings.py 里设置 STATIC_ROOT,然后执行收集:

STATIC_URL = "/static/" STATIC_ROOT = BASE_DIR / "static_collected"
python manage.py collectstatic --noinput

collectstatic 执行完毕后,admin 的 CSS 会出现在 static_collected/admin/ 下。开发环境下直接启用 runserver 通常正常,但一旦配置了 uWSGI 或 Nginx,漏掉这一步必然翻车。我见过最经典的案例是:本地打开 admin 一切正常,部署到服务器后样式全挂,排查半天才发现是 DEBUG=False 时静态文件根本没收集。

6. 进阶场景:领域标签自动匹配与三级复核机制

系统跑通之后,真正提高可用性的是两个进阶能力:给专家自动打领域标签,以及用复核队列控制数据质量。这两个功能不复杂,但能把“抽了一堆数据不敢用”变成“抽完就有信心用”。

6.1 领域标签自动匹配:关键词词典驱动的多对多写入

前面模型里设计了 fields 多对多关联。自动打标签的思路是:维护一个“领域→关键词”词典,在履历文本里做包含匹配,命中的领域就挂到专家记录上。

FIELD_KEYWORDS = { "人工智能": ["机器学习", "深度学习", "神经网络", "NLP", "计算机视觉"], "网络安全": ["攻防", "渗透", "加密", "零信任", "漏洞"], "数据库": ["分布式存储", "大数据", "数据仓库", "集群"], } def auto_tag_expert(expert): hit_fields = [] for field_name, keywords in FIELD_KEYWORDS.items(): if any(kw in (expert.profile or "") for kw in keywords): field_obj, _ = ResearchField.objects.get_or_create(name=field_name) hit_fields.append(field_obj) expert.fields.set(hit_fields) return hit_fields

这个方法的优点是词典可以随时扩充,命中率透明,缺点是依赖人工维护关键词。实际使用中我会把词典放到单独的 Python 文件里管理,不让它散落在视图代码中。匹配时用的any()只要命中一个关键词就算该领域命中,所以关键词要尽量专业和唯一,不要用“数据”这种太泛的词,否则所有专家都会被标成“数据库”。

6.2 用复核队列看抽取质量:置信度评分留出人工兜底

抽取系统的最终目标不是“全自动入库”,而是“高比例自动入库,低概率错误”。我给每一条抽取结果算一个分数,低于阈值就强制进入待复核状态。

def score_expert(expert): score = 0 score += 20 if expert.phone else 0 score += 20 if expert.email else 0 score += 20 if expert.title else 0 profile_len = len(expert.profile or "") score += min(40, profile_len // 10) if score >= 60: expert.status = "active" else: expert.status = "pending" expert.save(update_fields=["status"])

评分规则很简单:手机号、邮箱、职称各占 20 分,履历每 10 个字计 1 分,上限 40 分。总分数低于 60 的说明关键字段缺失太多,这些记录大概率是抽取不完整,必须人工看一遍。在实际项目里,我会在 admin 的 list_filter 里按 pending 状态过滤,每天花五分钟把待复核记录过一遍。这个习惯保证最终进库的数据是能拿出去直接用的,而不是一堆字段缺胳膊少腿的脏数据。从那以后我每次给这类系统收尾,都会强制走一遍“自动抽取 → 评分 → 人工复核”的闭环,别指望正则一次抽对,给人工留一条兜底路径才是负责任的做法。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询