农业知识图谱实战:从百度百科爬取到Neo4j可视化全流程
简介这份资源面向计算机、数学、电子信息等专业的学生与知识图谱初学者提供一套基于Neo4j的农业领域知识图谱构建完整源码可用于课程设计、期末大作业或毕设项目参考。项目覆盖从百度百科爬取农业数据、数据分类到结构化数据生成三元组、非结构化数据分句LTP、分词jieba、命名实体识别LTP以及基于依存句法分析主谓关系等抽取关系的全流程并最终在Neo4j中完成可视化呈现。压缩包共46个文件以23个txt文本、8个Python脚本、7个csv数据表为主另含xml配置、md说明等整体约21.41MB目录按茶叶、农作物、植物等主题分模块组织便于对照理解。目前已有87人学习下载。读者可借此掌握知识图谱从数据采集、清洗、实体关系抽取到图数据库落地的完整链路并参考其中的停用词表、词典与三元组结果文件进行调试与二次开发。1. 从一堆 CSV 到 Neo4j 图谱这套农业知识图谱源码到底能跑出什么手里有一批农业领域的文本散在 CSV、txt 和爬虫结果里想把它变成 Neo4j 里能点开、能查关系的可视化图谱中间缺的往往不是算法思路而是一条能跑通的流水线。这套源码解决的就是这件事从百度百科抓取农业词条做数据分类结构化数据直接生成三元组非结构化文本走 LTP 分句、jieba 分词、LTP 命名实体识别再用依存句法分析里的主谓关系等抽取三元组最后写进 Neo4j 出图。它适合做课程设计、毕设、算法源码参考的从业者也适合想拿一套完整链路练手的新手。我拆完的感受是链路完整但每一步都有参数和格式的坑照着跑之前得先搞清楚数据长什么样、代码在哪一步等你。2. 数据爬取与分类百度百科词条怎么变成可用的 CSV2.1 爬取脚本的入口与字段设计getData_from_baike.py是整条链路的起点。它的任务很明确给定一批农业相关词条名去百度百科抓取摘要、基本信息栏等文本落成 CSV。常见做法是用 requests 拿页面再用 BeautifulSoup 或 lxml 解析把词条名、摘要、基本信息键值对分别存列。这里要注意百度百科的页面结构会变选择器写死就容易翻车所以脚本里一般会把解析逻辑单独抽出来方便你换选择器。# getData_from_baike.py 核心逻辑示意 import requests from bs4 import BeautifulSoup import csv def fetch_baike(keyword): url https://baike.baidu.com/item/ keyword headers {User-Agent: Mozilla/5.0} resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, lxml) # 摘要通常在 meta 或特定 class 的 div 里按实际页面调整 summary soup.find(meta, {name: description}) summary_text summary[content] if summary else return {keyword: keyword, summary: summary_text} if __name__ __main__: keywords [茶树, 水稻, 小麦] rows [fetch_baike(k) for k in keywords] with open(my_datas.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[keyword, summary]) writer.writeheader() writer.writerows(rows)这段代码里timeout别省不然一个词条卡住整个脚本就挂在那里。encodingutf-8-sig是为了 Excel 打开不乱码后面 Neo4j 读取时用 utf-8 也能兼容。关键词列表建议单独放一个 txt方便替换成你自己的领域词。抓取频率要控制加个time.sleep(1)是基本礼貌也能降低被封的风险。2.2 数据分类结构化与非结构化的分水岭抓回来的数据不是一种形态。基本信息栏那种“键值”是结构化的摘要和正文段落是非结构化的。data_parser.py和get_struct_data.py干的就是分类和清洗。结构化数据直接映射成三元组比如“茶树 - 科 - 山茶科”非结构化数据得先分句再抽关系。分类没做好后面三元组就会混进一堆噪声。我一般会先看一眼my_datas_tea.csv、my_datas_crops.csv这类文件的列名和空值比例。如果某一列超过一半是空的说明抓取选择器可能失效了得回去改getData_from_baike.py。分类阶段还会用到停用词表项目里带了stopwords目录里面有百度停用词表.txt、中文停用词表.txt、四川大学机器智能实验室停用词库.txt等这些在分词和实体识别后过滤噪声时都会用到。提示停用词表不是越多越好农业领域里“种植”“产量”这类词如果被当停用词滤掉关系抽取会丢关键信息。建议先跑一遍看抽出来的三元组里有没有明显该保留的词被删了。3. 非结构化文本处理LTP 分句、jieba 分词与命名实体识别3.1 LTP 分句与 jieba 分词的分工非结构化文本进myLTP.py和triple_ie.py之前先要分句。LTP 的分句接口按标点切但农业文本里常有“亩产 500 公斤比去年增长 10%”这种带数字和百分号的句子切的时候要留意别把数字和单位切开。分句之后用 jieba 分词jiebadic.txt是自定义词典把“杂交水稻”“有机茶园”这类领域词加进去分词准确率会明显不一样。# myLTP.py 分句与分词示意 import jieba from ltp import LTP ltp LTP() # 默认加载小模型显存不够可换 tiny jieba.load_userdict(jiebadic.txt) def split_and_seg(text): # LTP 分句 sents ltp.sent_split([text]) results [] for sent in sents: words list(jieba.cut(sent)) results.append({sentence: sent, words: words}) return results if __name__ __main__: sample 茶树喜欢温暖湿润气候。亩产茶叶约 100 公斤。 for item in split_and_seg(sample): print(item[sentence], item[words])ltp.sent_split返回的是句子列表jieba.cut返回生成器转成 list 方便后面做词性标注和实体识别。jiebadic.txt里一行一个词可以带词频和词性比如“杂交水稻 100 n”。如果你发现“茶树”被切成“茶”和“树”就是词典没生效检查文件路径和编码。3.2 LTP 命名实体识别与依存句法分析抽三元组命名实体识别用 LTP 的ner接口能标出人名、地名、机构名等。农业领域更关心的是作物、病虫害、农药这类实体LTP 通用模型不一定全认所以项目里还配合了词典和规则。ner_results_tea.txt、ner_results_crops.txt这些文件就是识别结果落盘方便你核对哪些实体被漏了。依存句法分析是抽三元组的关键。主谓关系SBV、动宾关系VOB这些能帮你从“茶树 含有 茶多酚”里抽出茶树含有茶多酚。triple_ie.py里一般会遍历依存弧找 SBV 和 VOB 配对。# triple_ie.py 依存句法抽三元组示意 from ltp import LTP ltp LTP() def extract_triples(sentence): seg, hidden ltp.seg([sentence]) pos ltp.pos(hidden) ner ltp.ner(hidden) dep ltp.dep(hidden) words seg[0] triples [] # dep 格式: (head_index, relation, dependent_index) for head, rel, dep_idx in dep[0]: if rel SBV: # 找主语和谓语再找谓语后面的 VOB subject words[dep_idx - 1] predicate words[head - 1] for h2, r2, d2 in dep[0]: if r2 VOB and h2 head: obj words[d2 - 1] triples.append((subject, predicate, obj)) return triples if __name__ __main__: print(extract_triples(茶树含有茶多酚。))这里dep返回的索引是从 1 开始的取词的时候要减 1。SBV是主谓关系VOB是动宾关系不同版本的 LTP 关系标签可能略有差异跑之前先打印一条dep结果确认标签名。抽出来的三元组会写到triple_results_*.txt格式一般是“主语 谓语 宾语”用制表符或逗号分隔后面 Neo4j 导入脚本按这个格式解析。注意依存句法抽三元组对长句和复杂修饰语很敏感。“茶树在温暖湿润的气候下含有丰富的茶多酚”这种句子SBV 和 VOB 可能跨了多个词简单配对会抽错。常见做法是加一条规则谓语和宾语之间如果隔了“的”字结构先跳过或人工复核。4. 三元组落库 Neo4jcreateKG_neo4j.py 的参数与导入方式4.1 连接配置与节点关系建模createKG_neo4j.py是最后一步。它读triple_results_*.txt或最新三元组.txt用 py2neo 或 neo4j 官方驱动往图数据库里写。节点标签一般按实体类型分比如Crop、Disease、Chemical关系类型用谓语。如果谓语太杂可以先做一轮归一化把“含有”“包含”“富含”统一成CONTAINS。# createKG_neo4j.py 写入示意 from py2neo import Graph, Node, Relationship graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) def load_triples(path): triples [] with open(path, r, encodingutf-8) as f: for line in f: parts line.strip().split(\t) if len(parts) 3: triples.append(parts) return triples def write_to_neo4j(triples): for subj, pred, obj in triples: s_node Node(Entity, namesubj) o_node Node(Entity, nameobj) graph.merge(s_node, Entity, name) graph.merge(o_node, Entity, name) rel Relationship(s_node, pred.upper(), o_node) graph.merge(rel) if __name__ __main__: data load_triples(最新三元组.txt) write_to_neo4j(data)graph.merge比create安全重复节点不会报错。关系类型用pred.upper()是为了统一大小写Neo4j 关系类型对大小写敏感。密码别写死在代码里用环境变量或配置文件。如果三元组文件是逗号分隔把split(\t)改成split(,)。4.2 可视化查询与验证写完之后在 Neo4j Browser 里跑一句MATCH (n)-[r]-(m) RETURN n, r, m LIMIT 50能看到图就说明链路通了。如果节点全是Entity标签说明分类没做细可以按实体识别结果给节点打不同标签。查询“茶树”相关的两跳关系MATCH (n {name:茶树})-[r]-(m)-[r2]-(m2) RETURN n, r, m, r2, m2能快速验证图谱有没有断链。文件作用常见问题getData_from_baike.py爬取百科词条选择器失效、编码乱码myLTP.py分句、分词、NER模型加载慢、词典未生效triple_ie.py依存句法抽三元组关系标签不匹配、索引越界createKG_neo4j.py写入 Neo4j密码错误、关系类型大小写5. 避坑与排查跑这套源码最容易翻车的五个地方5.1 现象LTP 模型加载报错或卡住原因LTP 默认下载大模型网络不稳或显存不足时会卡在加载阶段。 解决换LTP(LTP/small)或 tiny 模型或者提前把模型文件下到本地用LTP(path本地路径)加载。跑之前先单独执行python -c from ltp import LTP; ltpLTP()确认模型能起来。5.2 现象三元组抽出来全是空或只有主语原因依存关系标签和代码里写的不一致比如某些版本用nsubj而不是SBV。 解决在triple_ie.py里加一行print(dep[0])看实际返回的关系标签再改判断条件。别照搬网上教程的标签名版本差异很常见。5.3 现象Neo4j 写入报“关系类型不能为空”原因三元组文件里有空行或分隔符不对split后长度不是 3。 解决在load_triples里加if len(parts) ! 3: continue同时检查文件是不是用制表符分隔。用cat -A 最新三元组.txt | head能看到实际分隔符。5.4 现象分词把领域词切碎原因jiebadic.txt没加载或路径不对。 解决确认jieba.load_userdict的路径是相对当前工作目录的建议用绝对路径。加载后跑一句print(list(jieba.cut(杂交水稻)))看是不是一个词。5.5 现象爬取的数据里混入无关词条原因关键词列表里有关联度低的词或者百科页面跳转到了消歧义页。 解决在getData_from_baike.py里加一层判断如果页面标题和关键词不一致就跳过。关键词列表人工过一遍别直接拿大词表跑。6. 进阶技巧把三元组质量再提一档的验证方法跑通之后真正决定图谱好不好用的是三元组质量。我一般会做两件事一是抽样人工核对从triple_results_*.txt里随机抽 50 条看主语、谓语、宾语是不是合理二是用 Neo4j 查询做一致性检查比如MATCH (n)-[r]-(m) WHERE n.name m.name RETURN n, r, m把自环关系揪出来这些多半是抽错了。另一个技巧是给关系加权重。同一对实体在多个句子里出现同一种关系可以在写入时累加一个count属性查询时按count排序高频关系优先展示。代码上就是在write_to_neo4j里先查有没有现成关系有就更新属性没有就新建。# 关系权重累加示意 def write_with_weight(triples): for subj, pred, obj in triples: s Node(Entity, namesubj) o Node(Entity, nameobj) graph.merge(s, Entity, name) graph.merge(o, Entity, name) rel Relationship(s, pred.upper(), o) existing graph.match_one(nodes(s, o), r_typepred.upper()) if existing: existing[count] existing.get(count, 1) 1 graph.push(existing) else: rel[count] 1 graph.create(rel)graph.match_one按节点和关系类型查查到就更新count查不到就新建。这样图谱里关系粗细可以按权重渲染一眼看出哪些关系最密集。从那以后我每次跑完三元组都会先抽 50 条人工过一遍再跑自环检查最后才写库。希望帮到你。本文还有配套的精品资源点击获取