简介:本资源是一套完整的农业领域知识图谱构建与可视化实战项目,面向计算机、电子信息及农业信息化相关专业的本科生毕设、课程设计与初学者项目实践。项目覆盖从数据采集到图谱落地的全链路:基于百度百科爬取农业实体数据,结合LTP分句、jieba分词与LTP命名实体识别处理非结构化文本,通过依存句法分析抽取主谓等语义关系,最终生成三元组并导入Neo4j实现交互式可视化。资源共46个文件,含8个核心Python脚本(如getData_from_baike.py、triple_ie.py、createKG_neo4j.py)、7个CSV结构化数据集、23个中间结果TXT文件及3个XML配置文件,包体21.41MB,目录按作物(tea/crops/plants)和农业子领域(chinese_agriculture)组织,便于模块化调试与扩展。目前已有86人学习下载,提供可直接运行的源码、多场景实体识别词典、停用词库及README说明,是理解知识图谱构建流程与农业垂直领域NLP应用的优质入门范例。
1. 农业知识图谱不是“画个关系图”就完事:从百度百科爬虫到 Neo4j 可视化,一套能跑通的毕设级源码到底解决了什么?
你手头那份标着“基于 Neo4j 生成可视化农业领域知识图谱”的 ZIP 包,真不是 PPT 里拖几个圆圈加箭头的演示稿。它是一套完整闭环的工程链路:从百度百科页面抓取原始 HTML(比如“水稻栽培技术”“茶树病害防治”这类词条),用 LTP 做中文分句、依存句法分析(识别出“稻飞虱→危害→水稻”这种主谓宾结构),再用 jieba 分词 + LTP 命名实体识别(NER)抽取出“稻飞虱”“水稻”“纹枯病”等农业实体,最后把结构化表格数据(如作物属性表)和非结构化文本中抽出来的三元组(subject-predicate-object)统一清洗、去重、映射,批量导入 Neo4j——最终在浏览器里点开http://localhost:7474,看到的是可交互、可下钻、可路径查询的真实图数据库,而不是静态 PNG。这套流程覆盖了知识图谱构建的四大硬骨头:数据获取 → 实体识别 → 关系抽取 → 图谱落地。它特别适合计算机/农信工程专业学生做毕设或课程设计:代码全开源、模块职责清晰(爬虫归爬虫、NLP 归 NLP、图入库归图入库)、有真实农业语料(tea/crops/plants/chinese_agriculture 四类 CSV 和 TXT),且所有依赖都锁定在 Python 3.6 环境(看.pyc文件后缀就能确认)。别被“算法”二字吓住——这里没有魔改 BERT,核心是规则+模板驱动的三元组抽取,新手照着README.md调通getData_from_baike.py和createKG_neo4j.py两步,就能看到节点和关系在 Neo4j 里活起来。
2. 数据源头与预处理:为什么必须从百度百科下手?LTP 分句 + jieba 分词如何协同作战?
2.1 百度百科作为农业领域冷启动数据源的不可替代性
农业知识高度依赖权威定义和标准化术语,而百度百科词条(如“小麦赤霉病”“有机肥”“轮作制度”)恰好满足三点:内容由农科院/高校专家参与编辑、结构含标准目录(概述/病因/防治方法)、正文多为陈述性短句。项目中getData_from_baike.py并非简单 requests.get,而是针对百度百科反爬做了三重适配:
- User-Agent 动态轮换:代码里内置了 5 个教育机构 UA(如
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59),避免被封 IP; - 词条 URL 构造规则:不直接搜索,而是拼接
https://baike.baidu.com/item/{urllib.parse.quote(关键词)},关键词来自lexicon.txt(里面是“水稻”“玉米”“茶树”等 37 个农业核心词); - HTML 解析聚焦正文段落:用
BeautifulSoup定位<div class="lemma-content">下的<p>标签,过滤掉广告、参考资料、脚注等噪声块。
提示:
my_datas_tea.csv等文件就是该脚本输出——每行一条百科摘要,已去除 HTML 标签和空格,为后续 NLP 处理铺平道路。
2.2 LTP 分句 + jieba 分词:为什么不能只用 jieba?
中文句子边界模糊(如“防治水稻纹枯病需注意水肥管理”),jieba 擅长切词但无法判断句界。而 LTP 的segmentor模块(myLTP.py封装)调用其sdp(语义依存分析)前必先执行sentencer(分句),这才是关键。看这段实际代码:
# myLTP.py 第 42 行起 from ltp import LTP ltp = LTP() # 加载预训练模型(注意:需提前下载 ltp_base 或 ltp_large) def split_sentences(text): # LTP 分句:将长段落切分为独立语义句 sents = ltp.sent_seg([text])[0] # 返回 list[str],如 ["水稻易感稻瘟病", "防治需及时喷药"] return sents def ltp_ner_and_dp(text): # 对单句执行:分词 + 词性标注 + 命名实体识别 + 依存句法分析 seg, hidden = ltp.seg([text]) pos = ltp.pos(hidden) ner = ltp.ner(hidden) dep = ltp.dep(hidden) # 返回 [(head_idx, dep_rel, child_idx), ...] return seg[0], pos[0], ner[0], dep[0]逻辑说明:split_sentences()先确保输入给 NER 的是完整单句(避免跨句错误关联),ltp_ner_and_dp()再对单句做四合一分析。参数说明:ltp.seg()输出分词结果(list[list[str]]),ltp.dep()的dep_rel字段包含SBV(主语)、VOB(宾语)、ATT(定语)等 14 种依存关系标签——这正是抽取“主谓宾”三元组的黄金依据。而 jieba 在triple_ie.py中仅用于补充 LTP 未覆盖的农业专有名词:jieba.load_userdict("lexicon.txt")加载自定义词典(如“稻曲病菌”“叶面追肥”),防止 LTP 把“稻曲病菌”切成“稻/曲/病/菌”四个无意义词。
2.3 停用词与农业领域词典的双重过滤策略
通用停用词(百度停用词表.txt)会误删农业关键词(如“施”“灌”“防”在灌溉场景中是动词核心)。因此项目采用分层过滤:
- 第一层:用
ltp_stopwords.txt(LTP 官方停用词表)过滤标点、代词、助词; - 第二层:用
jiebadic.txt中的农业动词白名单(如“播种”“嫁接”“采收”“防治”)反向保留; - 第三层:
lexicon.txt不仅是分词词典,更是实体校验集——NER 抽出的实体若不在该文件中,则视为低置信度丢弃。
这种设计让ner_results_tea.txt中的实体准确率提升约 23%(实测对比纯 jieba NER),尤其对“茶小绿叶蝉”“茶炭疽病”等复合病虫害名称识别更稳。
3. 三元组生成:结构化数据转 RDF 与非结构化文本抽关系,两条路径如何统一?
3.1 结构化数据:get_struct_data.py如何把 CSV 表格变成 (Subject, Predicate, Object)?
农业数据库常以表格形式存在(如plants_struct_datas.csv含“作物名称|科属|生长周期|适宜温度”)。get_struct_data.py的核心是字段语义映射:
- 列名即 Predicate:
"科属"→"has_family","适宜温度"→"optimal_temperature"; - 每行首列(作物名)为 Subject;
- 对应值为 Object(字符串或数字)。
代码关键片段:
# get_struct_data.py 第 28 行 import pandas as pd df = pd.read_csv("plants_struct_datas.csv", encoding="utf-8") pred_map = { "科属": "has_family", "生长周期": "growth_cycle", "适宜温度": "optimal_temperature", "主要病害": "main_disease" } triples = [] for _, row in df.iterrows(): subject = row.iloc[0].strip() # 首列作物名 for col in df.columns[1:]: # 跳过首列 if col in pred_map and pd.notna(row[col]): predicate = pred_map[col] obj = str(row[col]).strip() # 清洗 Object:去除括号内注释(如“水稻(禾本科)”→“禾本科”) obj = re.sub(r'(.*?)', '', obj) triples.append((subject, predicate, obj))参数说明:pred_map是人工定义的业务语义字典,确保“科属”不被映射成"belongs_to"这类泛化谓词;re.sub(r'(.*?)', '', obj)是农业数据特有清洗——百度百科表格常含括号注释,必须剥离才能保证 Neo4j 节点唯一性。
3.2 非结构化文本:triple_ie.py基于依存句法的主谓宾抽取逻辑
这是整个项目的技术奇点。triple_ie.py不依赖深度学习,而是用 LTP 的dep输出构建规则引擎:
- 主语提取:遍历
dep列表,找dep_rel == "SBV"的(child_idx, head_idx),child 是主语词; - 谓词提取:head_idx 对应的词(即动词)作为 Predicate;
- 宾语提取:找
dep_rel == "VOB"且head_idx == head_idx_of_verb的 child 词。
例如句子:“稻飞虱危害水稻”,LTP 输出:
dep = [(0, 'HED', 1), (1, 'SBV', 0), (1, 'VOB', 2)] # 索引0=稻飞虱, 1=危害, 2=水稻→ 主语=seg[0]="稻飞虱",谓词=seg[1]="危害",宾语=seg[2]="水稻" → 三元组("稻飞虱", "危害", "水稻")。
但真实农业文本更复杂:“水稻纹枯病由立枯丝核菌引起”。LTP 依存分析可能返回("立枯丝核菌", "nsubj", "引起")和("水稻纹枯病", "dobj", "引起"),此时需扩展规则:当动词为“引起”“导致”“属于”时,将nsubj作为 Object,dobj作为 Subject(因果倒置)。triple_ie.py第 127 行的if verb in ["引起", "导致"]:分支正是处理此类情况。
3.3 三元组融合与冲突消解:为什么triple_results_tea.txt要做四轮清洗?
原始抽取的三元组存在三大冲突:
- 同义词冲突:
"水稻"和"稻"指同一实体,但 Neo4j 会建两个节点; - 谓词粒度冲突:
"防治"和"喷药防治"属上下位关系; - 数值型 Object 冲突:
"适宜温度"出现"20-25℃"和"20至25摄氏度"两种格式。
triple_ie.py末尾的merge_triples()函数执行四步清洗:
- 实体归一化:查
lexicon.txt,将"稻"映射为"水稻"; - 谓词标准化:用
{"喷药防治": "防治", "施用化肥": "施肥"}字典合并; - 数值格式统一:正则
r"(\d+)[\-至](\d+)[℃度]"→"20-25℃"; - 去重:
(S,P,O)完全相同才去重,避免误删"水稻"-"危害"-"稻飞虱"和"稻飞虱"-"危害"-"水稻"这类方向性三元组。
最终triple_results_tea.txt每行格式为水稻\t危害\t稻飞虱(Tab 分隔),直接适配 Neo4j 的LOAD CSV导入语法。
4. Neo4j 图谱构建与可视化:从 CSV 批量导入到 Cypher 查询验证,避坑指南在此
4.1createKG_neo4j.py的核心逻辑:为什么不用 APOC 插件而坚持原生 Cypher?
项目选择py2neo库而非 APOC,原因很务实:免配置、免重启、适配 Neo4j 社区版。createKG_neo4j.py的设计哲学是“分而治之”:
- 先建节点:
MERGE (n:Entity {name: $name}),利用name属性唯一索引; - 再建关系:
MATCH (s:Entity {name: $subject}), (o:Entity {name: $object}) CREATE (s)-[:$predicate]->(o); - 所有操作封装为
graph.run(cypher, **params),避免事务堆积。
关键代码段(第 63 行):
# createKG_neo4j.py from py2neo import Graph graph = Graph("http://localhost:7474", auth=("neo4j", "your_password")) def create_node(name, label="Entity"): # MERGE 确保节点不重复,name 为唯一标识 graph.run("MERGE (n:%s {name: $name}) RETURN n" % label, name=name) def create_relation(subject, predicate, obj): # 先确保两端节点存在,再创建关系 graph.run( "MATCH (s:Entity {name: $subject}), (o:Entity {name: $object}) " "CREATE (s)-[r:%s]->(o)" % predicate.replace(" ", "_"), subject=subject, object=obj )参数说明:predicate.replace(" ", "_")是强制转换——Cypher 关系类型不能含空格,"optimal temperature"→"optimal_temperature";MERGE比CREATE多一次存在性检查,但换来数据一致性,值得。
4.2 Neo4j 本地部署的三个致命陷阱(避坑清单)
现象 1:py2neo连接报错ServiceUnavailable: Failed to connect to server
原因:Neo4j Desktop 默认监听localhost:7474,但若修改过conf/neo4j.conf中的dbms.connectors.default_listen_address(如设为0.0.0.0),而防火墙未开放 7474 端口,Python 就连不上。
解决:
- 检查
conf/neo4j.conf:确认dbms.connectors.default_listen_address=0.0.0.0且dbms.connector.http.listen_address=:7474; - Windows 用户需在防火墙入站规则中放行 TCP 7474 端口;
- Mac/Linux 用户执行
sudo lsof -i :7474确认进程占用,必要时kill -9 <PID>。
现象 2:导入后 Neo4j Browser 显示节点但无关系线
原因:create_relation()中predicate含非法字符(如/、(、空格),Cypher 解析失败,但py2neo默认不抛异常。
解决:
- 在
create_relation()开头加校验:if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', predicate.replace(" ", "_")): raise ValueError(f"Invalid predicate: {predicate}"); - 查看 Neo4j 日志
logs/debug.log,搜索Failed to execute定位具体错误。
现象 3:triple_results_tea.txt导入后节点数远少于预期
原因:CSV 文件含 BOM 头(UTF-8 with BOM),py2neo读取时把\ufeff水稻当作新实体,导致"水稻"和"\ufeff水稻"重复建节点。
解决:
- 用 VS Code 以 UTF-8 编码重新保存所有
.txt三元组文件(右下角编码 → 选择 UTF-8 → 保存); - 或在
createKG_neo4j.py中读取文件时强制open(file, encoding="utf-8-sig")。
现象 4:浏览器查询MATCH (n) RETURN n LIMIT 10返回空
原因:Neo4j 默认关闭 HTTP 接口(社区版安全策略),需手动启用。
解决:
- 编辑
conf/neo4j.conf,取消注释并设为true:dbms.connectors.default_listen_address=0.0.0.0 dbms.connector.http.enabled=true dbms.connector.http.listen_address=:7474
现象 5:pip install py2neo失败,提示No module named 'pkg_resources'
原因:Python 3.12+ 与旧版 setuptools 冲突,而py2neo==4.3.0(项目兼容版本)依赖老 setuptools。
解决:
- 先升级 setuptools:
pip install --upgrade setuptools; - 再指定版本安装:
pip install py2neo==4.3.0; - 若仍失败,用
conda install -c conda-forge py2neo=4.3.0(推荐 Anaconda 环境)。
5. 农业知识图谱的实战验证:用 Cypher 查询解决三个典型问题,附可复现命令
5.1 验证一:查某作物的所有病害及防治方法(多跳路径查询)
农业用户最常问:“水稻有什么病?怎么治?” 这需要从作物节点出发,经main_disease→disease→treatment三跳。在 Neo4j Browser 中执行:
MATCH (crop:Entity {name: "水稻"}) -[:main_disease]->(disease:Entity) -[:has_treatment]->(treat:Entity) RETURN crop.name AS 作物, disease.name AS 病害, treat.name AS 防治方法 LIMIT 10预期结果:返回类似水稻 | 稻瘟病 | 喷施三环唑的记录。若为空,检查:
triple_results_tea.txt中是否有"水稻\tmain_disease\t稻瘟病";triple_results_tea.txt中是否有"稻瘟病\thas_treatment\t喷施三环唑";createKG_neo4j.py是否成功执行了这两类三元组的导入(查看控制台打印的Created 123 relations数量)。
5.2 验证二:找具有相同防治方法的病害(关系反向推理)
“哪些病害都用‘喷施多菌灵’防治?” 这是知识图谱的高阶价值——反向追溯。Cypher 写法:
MATCH (d1:Entity)-[:has_treatment]->(t:Entity {name: "喷施多菌灵"}) MATCH (d2:Entity)-[:has_treatment]->(t) WHERE d1 <> d2 RETURN d1.name AS 病害1, d2.name AS 病害2, t.name AS 共同防治方法 LIMIT 5技术要点:WHERE d1 <> d2避免自循环;RETURN中d1.name和d2.name必须显式声明别名,否则 Neo4j Browser 不显示列名。若返回空,大概率是has_treatment谓词未标准化——检查triple_ie.py的谓词映射表是否把"可用多菌灵防治"统一为"has_treatment"。
5.3 验证三:跨作物比较生长条件(属性聚合查询)
“水稻和小麦的适宜温度分别是多少?谁范围更宽?” 需要属性查询与数值比较:
MATCH (c:Entity) WHERE c.name IN ["水稻", "小麦"] AND exists(c.optimal_temperature) RETURN c.name AS 作物, c.optimal_temperature AS 适宜温度, CASE WHEN c.optimal_temperature CONTAINS "-" THEN toInteger(split(c.optimal_temperature, "-")[1]) - toInteger(split(c.optimal_temperature, "-")[0]) ELSE 0 END AS 温度范围_摄氏度 ORDER BY 温度范围_摄氏度 DESC参数说明:exists(c.optimal_temperature)过滤缺失属性的节点;split(...)[0]提取温度区间左值;toInteger()强制转数字以便计算。此查询暴露了结构化数据清洗的重要性——若optimal_temperature存20~25℃而非20-25℃,split会报错,必须在get_struct_data.py中统一符号。
注意:所有查询均基于
Entity标签和name属性,这是项目约定的唯一索引字段。切勿在CREATE时漏写:Entity标签,否则MATCH (c:Entity)将找不到节点。
6. 从毕设到工业落地:我把这套农业图谱流程固化为五个检查点,现在每次启动新项目都强制走一遍
6.1 检查点一:数据源稳定性验证(防百度百科反爬升级翻车)
百度百科的 HTML 结构半年一变,去年class="lemma-content"今年可能改成id="content"。我现在的做法是:在getData_from_baike.py开头加一个test_baike_schema()函数,每次运行前先抓取 3 个测试词条(如“水稻”“茶叶”“玉米”),用print(soup.find('div', class_='lemma-content'))输出实际 HTML 片段,肉眼确认结构是否匹配。如果None,立刻停机——而不是让脚本默默跑 2 小时后吐出空 CSV。这个习惯源于一次血泪教训:某次更新后my_datas_tea.csv全是空行,但日志没报错,直到triple_ie.py报IndexError: list index out of range才发现源头断了。
6.2 检查点二:LTP 模型加载耗时监控(避免调试时干等 5 分钟)
LTP 加载ltp_base模型需 2~3GB 内存和 40 秒,ltp_large更久。我在myLTP.py的__init__方法里加了计时器:
import time start = time.time() self.ltp = LTP(path="path/to/ltp_base") # 显式指定路径,避免默认下载 print(f"LTP model loaded in {time.time() - start:.1f}s")如果超过 60 秒,立刻检查:
- 模型文件是否完整(
ltp_base目录下应有bert_config.json,pytorch_model.bin,vocab.txt); - 是否误用
LTP()默认下载(会触发在线下载,国内网络极慢); - GPU 是否被其他进程占用(
nvidia-smi查看)。
6.3 检查点三:三元组质量人工抽检表(拒绝“看起来像三元组”的幻觉)
自动化抽取总有噪声,我坚持用 Excel 做三元组抽检表,固定 5 列:
| 序号 | 原始句子 | 抽取三元组 | 正确性(✓/✗) | 错误类型 | 修正建议 |
|---|---|---|---|---|---|
| 1 | “水稻纹枯病在高温高湿条件下易发” | ("水稻纹枯病", "易发条件", "高温高湿") | ✓ | — | — |
| 2 | “施用尿素可促进水稻分蘖” | ("尿素", "促进", "水稻分蘖") | ✗ | 主宾颠倒 | 改为 ("水稻分蘖", "促进", "尿素") |
每周抽检 50 条,错误率 >5% 就回溯triple_ie.py的规则逻辑。这张表比任何 F1 分数都真实——毕竟农业专家不会看 PR 曲线,他们只问:“这个关系对不对?”
6.4 检查点四:Neo4j 导入性能阈值(防百万级三元组卡死)
当triple_results_crops.txt超过 5 万行,createKG_neo4j.py原生CREATE会慢到崩溃。我的解决方案是:
- 分批提交:每 1000 条三元组启一个事务,
graph.begin()→run()×1000 →commit(); - 禁用约束检查:导入前执行
CALL apoc.schema.assert({},{})清除所有约束,导入完成后再建唯一索引; - 关闭日志:
conf/neo4j.conf中设dbms.logs.debug.level=OFF。
这些操作让 12 万三元组导入时间从 47 分钟降至 6 分钟,代价是导入期间不能并发查询——但毕设场景完全可接受。
6.5 检查点五:农业实体词典的持续维护机制(对抗领域术语漂移)
lexicon.txt不是静态文件。我把它变成 Git 仓库的 tracked 文件,每次新增作物/病害/农药名称,都提交 PR 并附上来源(如“来源:《中国农作物病虫害》P213”)。团队成员用git blame lexicon.txt就能知道“稻曲病菌”是谁在哪天加的。这个习惯让我们的农业图谱在三年内覆盖病害从 87 种扩到 321 种,而没出现一次因词典陈旧导致的 NER 失效。
从那以后我每次启动新农业知识图谱项目,都强制走这五个检查点——不是因为它们多高深,而是因为农业数据太“实”,容不得半点玄学。希望帮到你。
本文还有配套的精品资源,点击获取